diff --git a/.github/workflows/build-admin-ui.yaml b/.github/workflows/build-admin-ui.yaml index fd43a206c0..4361c54540 100644 --- a/.github/workflows/build-admin-ui.yaml +++ b/.github/workflows/build-admin-ui.yaml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + - uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 id: node-modules-cache with: path: "**/node_modules" @@ -29,7 +29,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + - uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 id: node-modules-cache with: path: "**/node_modules" @@ -42,12 +42,12 @@ jobs: node-version: 20 - run: yarn run build:ui:admin:oss - name: Upload artifact admin-ui - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: admin-ui path: ./ui/admin/dist/ - name: Upload artifact OSS admin UI - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: admin-ui-oss path: ./ui/admin/dist/ @@ -57,7 +57,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + - uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 id: node-modules-cache with: path: "**/node_modules" @@ -70,7 +70,7 @@ jobs: node-version: 20 - run: yarn run build:ui:admin:enterprise - name: Upload artifact enterprise admin UI - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: admin-ui-ent path: ./ui/admin/dist/ @@ -80,7 +80,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + - uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 id: node-modules-cache with: path: "**/node_modules" @@ -93,7 +93,7 @@ jobs: node-version: 20 - run: yarn run build:ui:admin:hcp - name: Upload artifact HCP admin UI - uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 + uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0 with: name: admin-ui-hcp path: ./ui/admin/dist/ diff --git a/.github/workflows/build-artifacts-for-testing-trigger.yml b/.github/workflows/build-artifacts-for-testing-trigger.yml deleted file mode 100644 index 13fa136737..0000000000 --- a/.github/workflows/build-artifacts-for-testing-trigger.yml +++ /dev/null @@ -1,34 +0,0 @@ -name: "Trigger Build Artifacts for Testing" - -on: - workflow_dispatch: - inputs: - branch: - description: Please use tag-like format n.n.n-someword (1.111.11-beta) - required: true - product_version: - required: true - type: string - description: The version number belonging to the oss binary to be used - commit: - required: true - type: string - description: The git SHA of the oss artifacts to be used - -jobs: - trigger-gha-build-artifacts-for-testing: - runs-on: ubuntu-latest - steps: - - name: Release description - if: github.event_name == 'workflow_dispatch' - run: | - echo "Triggering Build Artifacts for Testing using BRANCH and TEST_TAG ${{ github.event.inputs.branch }}" - - name: Trigger Build Artifacts for Testing in boundary-desktop-releases - run: | - export GITHUB_TOKEN="${{ secrets.ELEVATED_GITHUB_TOKEN }}" - gh workflow run build-artifacts-for-testing.yml \ - --repo hashicorp/boundary-desktop-releases \ - --ref main \ - -f TAG="${{ inputs.branch }}" \ - -f PRODUCT_VERSION="${{ inputs.product_version }}" \ - -f COMMIT="${{ inputs.commit }}" \ \ No newline at end of file diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 93b5f7a3a5..a2d82dae3b 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -34,7 +34,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@af56b044b5d41c317aef5d19920b3183cb4fbbec # codeql-bundle-v2.19.2 + uses: github/codeql-action/init@3096afedf9873361b2b2f65e1445b13272c83eb8 # codeql-bundle-v2.20.0 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -54,4 +54,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@af56b044b5d41c317aef5d19920b3183cb4fbbec # codeql-bundle-v2.19.2 + uses: github/codeql-action/analyze@3096afedf9873361b2b2f65e1445b13272c83eb8 # codeql-bundle-v2.20.0 diff --git a/.github/workflows/monorepo-validate.yaml b/.github/workflows/monorepo-validate.yaml index feaa2ed95e..bbf497919c 100644 --- a/.github/workflows/monorepo-validate.yaml +++ b/.github/workflows/monorepo-validate.yaml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + - uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 id: node-modules-cache with: path: '**/node_modules' @@ -25,7 +25,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + - uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 id: node-modules-cache with: path: '**/node_modules' @@ -44,7 +44,7 @@ jobs: timeout-minutes: 5 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + - uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 id: node-modules-cache with: path: '**/node_modules' @@ -63,7 +63,7 @@ jobs: timeout-minutes: 15 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: actions/cache@6849a6489940f00c2f30c0fb92c6274307ccb58a # v4.1.2 + - uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 id: node-modules-cache with: path: '**/node_modules' diff --git a/.github/workflows/test-release-trigger.yml b/.github/workflows/test-release-trigger.yml deleted file mode 100644 index 24aa5ccae0..0000000000 --- a/.github/workflows/test-release-trigger.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: "Trigger Test Boundary Desktop release" - -on: - workflow_dispatch: - inputs: - branch: - description: Please use tag-like format n.n.n-someword (1.111.11-beta) - required: true - -jobs: - trigger-gha-release: - runs-on: ubuntu-latest - steps: - - name: Release description - if: github.event_name == 'workflow_dispatch' - run: | - echo "Triggering Test release using BRANCH and TEST_TAG ${{ github.event.inputs.branch }}" - - name: Trigger Release workflow in boundary-desktop-releases - run: | - export GITHUB_TOKEN="${{ secrets.ELEVATED_GITHUB_TOKEN }}" - gh workflow run release.yml \ - --repo hashicorp/boundary-desktop-releases \ - --ref main \ - -f TAG="${{ github.event.inputs.branch }}" - diff --git a/addons/api/addon/generated/models/host-catalog.js b/addons/api/addon/generated/models/host-catalog.js index 585b8031be..007d8587c8 100644 --- a/addons/api/addon/generated/models/host-catalog.js +++ b/addons/api/addon/generated/models/host-catalog.js @@ -55,26 +55,37 @@ export default class GeneratedHostCatalogModel extends BaseModel { }) plugin; - // AWS & Azure + // AWS, Azure & GCP @attr('boolean', { - for: 'plugin', + for: { + type: 'plugin', + name: ['aws', 'azure', 'gcp'], + }, isNestedAttribute: true, description: '', }) disable_credential_rotation; - // AWS specific + // AWS and GCP specific @attr('string', { - for: 'plugin', + for: { + type: 'plugin', + name: ['aws', 'gcp'], + }, description: 'An expression used to filter the workers that have network access to a service that is hosting the external object store.', }) worker_filter; + // AWS specific + @attr('string', { - for: 'plugin', + for: { + type: 'plugin', + name: 'aws', + }, isNestedAttribute: true, description: '', }) @@ -82,14 +93,22 @@ export default class GeneratedHostCatalogModel extends BaseModel { // AWS static credentials @attr('string', { - for: 'plugin', + for: { + type: 'plugin', + name: 'aws', + credentialType: 'static-credential', + }, isNestedSecret: true, description: '', }) access_key_id; @attr('string', { - for: 'plugin', + for: { + type: 'plugin', + name: 'aws', + credentialType: 'static-credential', + }, isNestedSecret: true, description: '', }) @@ -97,28 +116,44 @@ export default class GeneratedHostCatalogModel extends BaseModel { // AWS dynamic credentials @attr('string', { - for: 'plugin', + for: { + type: 'plugin', + name: 'aws', + credentialType: 'dynamic-credential', + }, isNestedAttribute: true, description: 'The role ARN to use.', }) role_arn; @attr('string', { - for: 'plugin', + for: { + type: 'plugin', + name: 'aws', + credentialType: 'dynamic-credential', + }, isNestedAttribute: true, description: 'The role external ID to use.', }) role_external_id; @attr('string', { - for: 'plugin', + for: { + type: 'plugin', + name: 'aws', + credentialType: 'dynamic-credential', + }, isNestedAttribute: true, description: 'The role session to use.', }) role_session_name; @attr('object-as-array', { - for: 'plugin', + for: { + type: 'plugin', + name: 'aws', + credentialType: 'dynamic-credential', + }, isNestedAttribute: true, description: 'The role tags to use.', }) @@ -127,37 +162,117 @@ export default class GeneratedHostCatalogModel extends BaseModel { // Azure specific @attr('string', { - for: 'plugin', + for: { + type: 'plugin', + name: 'azure', + }, isNestedAttribute: true, description: '', }) tenant_id; @attr('string', { - for: 'plugin', + for: { + type: 'plugin', + name: 'azure', + }, isNestedAttribute: true, description: '', }) client_id; @attr('string', { - for: 'plugin', + for: { + type: 'plugin', + name: 'azure', + }, isNestedAttribute: true, description: '', }) subscription_id; @attr('string', { - for: 'plugin', + for: { + type: 'plugin', + name: 'azure', + }, isNestedSecret: true, description: '', }) secret_id; @attr('string', { - for: 'plugin', + for: { + type: 'plugin', + name: 'azure', + }, isNestedSecret: true, description: '', }) secret_value; + + // GCP specific + @attr('string', { + for: { + type: 'plugin', + name: 'gcp', + }, + isNestedAttribute: true, + description: 'The project ID associated with the service account.', + }) + project_id; + + @attr('string', { + for: { + type: 'plugin', + name: 'gcp', + }, + isNestedAttribute: true, + description: 'The deployment area within a region.', + }) + zone; + + @attr('string', { + for: { + type: 'plugin', + name: 'gcp', + }, + isNestedAttribute: true, + description: + 'The email address associated with the service account. The email address used to uniquely identify the service account. It is required for authentication and authorization.', + }) + client_email; + + @attr('string', { + for: { + type: 'plugin', + name: 'gcp', + }, + isNestedAttribute: true, + description: + 'The unique identifier for the service account that will be impersonated.', + }) + target_service_account_id; + + @attr('string', { + for: { + type: 'plugin', + name: 'gcp', + }, + description: + 'The ID of the private key used to sign the JWT and authentication.', + isNestedSecret: true, + }) + private_key_id; + + @attr('string', { + for: { + type: 'plugin', + name: 'gcp', + }, + description: + 'The private key used to sign the JWT and obtain a OAuth 2.0 access token.', + isNestedSecret: true, + }) + private_key; } diff --git a/addons/api/addon/generated/models/host-set.js b/addons/api/addon/generated/models/host-set.js index 03d64f997f..31a126d45a 100644 --- a/addons/api/addon/generated/models/host-set.js +++ b/addons/api/addon/generated/models/host-set.js @@ -63,6 +63,10 @@ export default class GeneratedHostSetModel extends BaseModel { plugin; @attr('number', { + for: { + type: 'plugin', + name: ['aws', 'azure', 'gcp'], + }, description: "The number of seconds between the time boundary syncs the hosts in this set using this host set's plugin. If not provided a system determined default is used", }) @@ -79,12 +83,20 @@ export default class GeneratedHostSetModel extends BaseModel { host_ids; @attr('string-array', { + for: { + type: 'plugin', + name: ['aws', 'azure', 'gcp'], + }, emptyArrayIfMissing: true, }) preferred_endpoints; - // AWS specific + // AWS & GCP specific @attr('string-array', { + for: { + type: 'plugin', + name: ['aws', 'gcp'], + }, isNestedAttribute: true, emptyArrayIfMissing: true, }) @@ -92,6 +104,10 @@ export default class GeneratedHostSetModel extends BaseModel { // Azure specific, comes in from API as "filter". This is to avoid collisions with filter @attr('string', { + for: { + type: 'plugin', + name: 'azure', + }, description: '', isNestedAttribute: true, }) diff --git a/addons/api/addon/models/credential.js b/addons/api/addon/models/credential.js index 63ed31628b..e4e4bd21b0 100644 --- a/addons/api/addon/models/credential.js +++ b/addons/api/addon/models/credential.js @@ -8,7 +8,16 @@ import GeneratedCredentialModel from '../generated/models/credential'; /** * Supported Credential types. */ -export const types = ['username_password', 'ssh_private_key', 'json']; + +export const TYPE_CREDENTIAL_USERNAME_PASSWORD = 'username_password'; +export const TYPE_CREDENTIAL_SSH_PRIVATE_KEY = 'ssh_private_key'; +export const TYPE_CREDENTIAL_JSON = 'json'; + +export const TYPES_CREDENTIAL = Object.freeze([ + TYPE_CREDENTIAL_USERNAME_PASSWORD, + TYPE_CREDENTIAL_SSH_PRIVATE_KEY, + TYPE_CREDENTIAL_JSON, +]); export default class CredentialModel extends GeneratedCredentialModel { // =attributes @@ -28,7 +37,7 @@ export default class CredentialModel extends GeneratedCredentialModel { * @type {boolean} */ get isJSON() { - return this.type === 'json'; + return this.type === TYPE_CREDENTIAL_JSON; } /** @@ -36,6 +45,6 @@ export default class CredentialModel extends GeneratedCredentialModel { * @type {boolean} */ get isUnknown() { - return !types.includes(this.type); + return !TYPES_CREDENTIAL.includes(this.type); } } diff --git a/addons/api/addon/models/host-catalog.js b/addons/api/addon/models/host-catalog.js index e8b47a2ac6..f220fd56ea 100644 --- a/addons/api/addon/models/host-catalog.js +++ b/addons/api/addon/models/host-catalog.js @@ -14,9 +14,11 @@ export const TYPES_HOST_CATALOG = Object.freeze([ export const TYPE_HOST_CATALOG_PLUGIN_AWS = 'aws'; export const TYPE_HOST_CATALOG_PLUGIN_AZURE = 'azure'; +export const TYPE_HOST_CATALOG_PLUGIN_GCP = 'gcp'; export const TYPES_HOST_CATALOG_PLUGIN = [ TYPE_HOST_CATALOG_PLUGIN_AWS, TYPE_HOST_CATALOG_PLUGIN_AZURE, + TYPE_HOST_CATALOG_PLUGIN_GCP, ]; export const TYPE_CREDENTIAL_STATIC = 'static-credential'; @@ -27,12 +29,13 @@ export const TYPES_CREDENTIALS = Object.freeze([ TYPE_CREDENTIAL_DYNAMIC, ]); -const DYNAMIC_CREDENTIAL_FIELDS = [ +export const DYNAMIC_CREDENTIAL_FIELDS = [ 'role_arn', 'role_external_id', 'role_session_name', 'role_tags', ]; +export const STATIC_CREDENTIAL_FIELDS = ['access_key_id, secret_access_key']; export default class HostCatalogModel extends GeneratedHostCatalogModel { // =attributes @@ -92,7 +95,7 @@ export default class HostCatalogModel extends GeneratedHostCatalogModel { * @type {boolean} */ get isAWS() { - return this.compositeType === 'aws'; + return this.compositeType === TYPE_HOST_CATALOG_PLUGIN_AWS; } /** @@ -100,7 +103,15 @@ export default class HostCatalogModel extends GeneratedHostCatalogModel { * @type {boolean} */ get isAzure() { - return this.compositeType === 'azure'; + return this.compositeType === TYPE_HOST_CATALOG_PLUGIN_AZURE; + } + + /** + * True if host catalog plugin type is GCP. + * @type {boolean} + */ + get isGCP() { + return this.compositeType === TYPE_HOST_CATALOG_PLUGIN_GCP; } /** diff --git a/addons/api/addon/models/host-set.js b/addons/api/addon/models/host-set.js index c5895021d7..9239dc811f 100644 --- a/addons/api/addon/models/host-set.js +++ b/addons/api/addon/models/host-set.js @@ -4,7 +4,12 @@ */ import GeneratedHostSetModel from '../generated/models/host-set'; -import { TYPES_HOST_CATALOG_PLUGIN } from './host-catalog'; +import { + TYPES_HOST_CATALOG_PLUGIN, + TYPE_HOST_CATALOG_PLUGIN_GCP, + TYPE_HOST_CATALOG_PLUGIN_AWS, + TYPE_HOST_CATALOG_PLUGIN_AZURE, +} from './host-catalog'; export default class HostSetModel extends GeneratedHostSetModel { // =attributes @@ -40,14 +45,22 @@ export default class HostSetModel extends GeneratedHostSetModel { * @type {boolean} */ get isAWS() { - return this.compositeType === 'aws'; + return this.compositeType === TYPE_HOST_CATALOG_PLUGIN_AWS; } /** * Return if a host-set plugin is Azure or not. */ get isAzure() { - return this.compositeType === 'azure'; + return this.compositeType === TYPE_HOST_CATALOG_PLUGIN_AZURE; + } + + /** + * True if host catalog plugin type is GCP. + * @type {boolean} + */ + get isGCP() { + return this.compositeType === TYPE_HOST_CATALOG_PLUGIN_GCP; } /** diff --git a/addons/api/addon/models/session.js b/addons/api/addon/models/session.js index dc9bc9275f..7ec0b10b1a 100644 --- a/addons/api/addon/models/session.js +++ b/addons/api/addon/models/session.js @@ -8,6 +8,9 @@ import { equal } from '@ember/object/computed'; import { tracked } from '@glimmer/tracking'; import { A } from '@ember/array'; import { inject as service } from '@ember/service'; +import { flattenObject } from '../utils/flatten-nested-object'; +import { TYPES_CREDENTIAL_LIBRARY } from 'api/models/credential-library'; +import { TYPE_CREDENTIAL_JSON } from 'api/models/credential'; export const STATUS_SESSION_ACTIVE = 'active'; export const STATUS_SESSION_PENDING = 'pending'; @@ -23,7 +26,7 @@ export const statusTypes = [ /** * */ -class SessionCredential { +export class SessionCredential { // =classes /** @@ -34,12 +37,14 @@ class SessionCredential { name; description; type; + credentialtype; - constructor(id, name, description, type) { + constructor(id, name, description, type, credentialtype) { this.id = id; this.name = name; this.description = description; this.type = type; + this.credentialType = credentialtype; } }; @@ -75,8 +80,10 @@ class SessionCredential { get secrets() { if (this.#payloadSecret?.decoded) { const secretJSON = this.#payloadSecret.decoded; - return Object.keys(secretJSON).map( - (key) => new SessionCredential.SecretItem(key, secretJSON[key]), + return this.extractSecrets( + secretJSON, + this.source.type, + this.source.credentialType, ); } else { // decode from base64 @@ -85,11 +92,41 @@ class SessionCredential { } } + /** + * Extracts secrets from the payload secret JSON object. + * We only need to flatten the object if the type is vault or if its a json type credential. + * @param {object} secretJSON - The payload secret JSON object. + * @param {string} type - The credential source type. + * @param {string} credentialType - The credential type. + * @returns {SessionCredential.SecretItem[]} - The array of secret items. + */ + extractSecrets(secretJSON, type, credentialType) { + let source; + if (credentialType === TYPE_CREDENTIAL_JSON) { + source = flattenObject(secretJSON); + } else if (TYPES_CREDENTIAL_LIBRARY.includes(type) && secretJSON?.data) { + source = flattenObject(secretJSON.data); + } else { + source = secretJSON; + } + + return Object.entries(source).map( + ([key, value]) => new SessionCredential.SecretItem(key, value), + ); + } + // =methods constructor(cred) { - const { id, name, description, type } = cred.credential_source; - this.source = new SessionCredential.Source(id, name, description, type); + const { id, name, description, type, credential_type } = + cred.credential_source; + this.source = new SessionCredential.Source( + id, + name, + description, + type, + credential_type, + ); this.#payloadSecret = cred.secret; this.rawCredential = cred; } diff --git a/addons/api/addon/serializers/application.js b/addons/api/addon/serializers/application.js index a6f46e402d..d1c494ffa7 100644 --- a/addons/api/addon/serializers/application.js +++ b/addons/api/addon/serializers/application.js @@ -6,6 +6,7 @@ import RESTSerializer from '@ember-data/serializer/rest'; import { underscore } from '@ember/string'; import { get } from '@ember/object'; +import { typeOf } from '@ember/utils'; /** * Manages serialization/normalization of data to/from the API. @@ -42,7 +43,7 @@ export default class ApplicationSerializer extends RESTSerializer { */ serializeAttribute(snapshot, json, key, attribute) { const { type, options } = attribute; - const { type: recordType } = snapshot.record; + const { type: recordType, compositeType } = snapshot.record; let value = super.serializeAttribute(...arguments); // Remove secret attributes that are null or empty if (options.isSecret) { @@ -74,18 +75,38 @@ export default class ApplicationSerializer extends RESTSerializer { delete json[key]; } // If an attribute has a `for` option, it must match the - // record's `type`, else the attribute is excluded + // record's `type`, otherwise the attribute is excluded // from serialization. - //note: This doesn't handle secrets yet, we can add support for them if needed. - if ( - options?.for && - options.for !== recordType && - !options.for.includes(recordType) - ) { - if (options.isNestedAttribute) { - delete json?.attributes?.[key]; - } else { - delete json[key]; + + if (options?.for) { + // Check the typeof the `for` option to determine if it is a plugin or not + const isPlugin = options.for?.type === 'plugin'; + // Helper function to handle deleting attribute and secret keys + const deleteKey = (json) => { + if (options.isNestedAttribute) { + delete json?.attributes?.[key]; + } else if (options.isNestedSecret && json?.secrets?.[key]) { + delete json?.secrets?.[key]; + if (Object.keys(json.secrets).length === 0) { + delete json.secrets; + } + } else { + delete json[key]; + } + }; + + // For plugins, the `for` option is an object with a `name` key + // For non-plugins, the `for` option can be string or an array + if ( + (isPlugin && !options.for?.name.includes(compositeType)) || + (!isPlugin && + !options.for?.includes(recordType) && + typeOf(options.for) === 'array') || + (!isPlugin && + options.for !== recordType && + typeOf(options.for) === 'string') + ) { + deleteKey(json); } } return value; diff --git a/addons/api/addon/serializers/host-catalog.js b/addons/api/addon/serializers/host-catalog.js index ff885c8afb..373f5eee94 100644 --- a/addons/api/addon/serializers/host-catalog.js +++ b/addons/api/addon/serializers/host-catalog.js @@ -4,115 +4,45 @@ */ import ApplicationSerializer from './application'; -import { - TYPE_HOST_CATALOG_PLUGIN_AWS, - TYPE_HOST_CATALOG_PLUGIN_AZURE, - TYPE_CREDENTIAL_DYNAMIC, -} from '../models/host-catalog'; - -// These fields are Azure specific -const azureFields = [ - 'disable_credential_rotation', - 'tenant_id', - 'client_id', - 'subscription_id', - 'secret_id', - 'secret_value', -]; - -// These fields are AWS specific, AWS provider now has two credentialTypes: static and dynamic [not to be confused with static and dynamic host-catalogs] -const AWSfieldsWithCredentialType = { - 'static-credential': [ - 'access_key_id', - 'secret_access_key', - 'region', - 'disable_credential_rotation', - 'worker_filter', - ], - 'dynamic-credential': [ - 'role_arn', - 'role_external_id', - 'role_session_name', - 'role_tags', - 'region', - 'disable_credential_rotation', - 'worker_filter', - ], -}; +import { TYPE_CREDENTIAL_DYNAMIC } from '../models/host-catalog'; export default class HostCatalogSerializer extends ApplicationSerializer { serialize(snapshot) { - const { compositeType, credentialType } = snapshot.record; + const { credentialType } = snapshot.record; const serialized = super.serialize(...arguments); - - // This is a hack to remove the worker_filter field from the serialized data if the compositeType is not AWS - // Ideally we should refactor the model to include the compositeType using `for` attribute - if (compositeType !== TYPE_HOST_CATALOG_PLUGIN_AWS) { - delete serialized.worker_filter; - } - // By default, disable credential rotation for dynamic credentials if (credentialType === TYPE_CREDENTIAL_DYNAMIC) { serialized.attributes.disable_credential_rotation = true; } - - switch (compositeType) { - case 'static': - return this.serializeStatic(...arguments); - default: - return serialized; - } + return serialized; } serializeAttribute(snapshot, json, key, attribute) { const value = super.serializeAttribute(...arguments); - const { compositeType, credentialType, isPlugin } = snapshot.record; + const { compositeType, credentialType } = snapshot.record; const { options } = attribute; - const fields = - compositeType === TYPE_HOST_CATALOG_PLUGIN_AZURE - ? azureFields - : AWSfieldsWithCredentialType[credentialType]; - - // Delete any nested attribute fields that don't belong to the record's compositeType - if (isPlugin && options.isNestedAttribute && json.attributes) { - // If compositeType is AWS, delete azure fields from the json, and set the rest that don't belong to respective compositeType to null - if (compositeType === TYPE_HOST_CATALOG_PLUGIN_AWS) { - if (!AWSfieldsWithCredentialType[credentialType].includes(key)) { - if (azureFields.includes(key)) { - delete json.attributes[key]; - } else { - json.attributes[key] = null; - } - } - } - - // If compositeType is Azure, delete AWS fields from the json - if ( - compositeType === TYPE_HOST_CATALOG_PLUGIN_AZURE && - !azureFields.includes(key) - ) { - delete json.attributes[key]; - } - } - - // Delete any secret fields that don't belong to the record type - if (isPlugin && options.isNestedSecret && json.secrets) { - if (!fields.includes(key)) { + const isSamePluginName = options?.for?.name?.includes(compositeType); + + // Remove attributes that are not applicable to the current credential type for the same plugin + // For example, 'aws' dynamic plugin has two credential types: 'static' and 'dynamic' + // API expects the fields to be 'null' for the fields that are not from the current credential type (that's why we are not using the application serializer for this, where we delete attrs) + // Empty secrets should be removed from the JSON + if ( + isSamePluginName && + options.for.credentialType && + options.for.credentialType !== credentialType + ) { + if (options.isNestedAttribute) { + json.attributes[key] = null; + } else if (options.isNestedSecret && json.secrets[key]) { delete json.secrets[key]; - if (json['secrets'] && Object.keys(json.secrets).length === 0) { + if (Object.keys(json.secrets).length === 0) { delete json.secrets; } + } else { + delete json[key]; } } return value; } - - serializeStatic() { - const serialized = super.serialize(...arguments); - // Delete unnecessary fields for static host-catalog - delete serialized.attributes; - delete serialized.secrets; - - return serialized; - } } diff --git a/addons/api/addon/serializers/host-set.js b/addons/api/addon/serializers/host-set.js index c2ef41c52a..6e303ce14f 100644 --- a/addons/api/addon/serializers/host-set.js +++ b/addons/api/addon/serializers/host-set.js @@ -5,10 +5,6 @@ import ApplicationSerializer from './application'; -const fieldByType = { - aws: ['preferred_endpoints', 'filters', 'sync_interval_seconds'], - azure: ['preferred_endpoints', 'filter_string', 'sync_interval_seconds'], -}; export default class HostSetSerializer extends ApplicationSerializer { // =properties @@ -63,16 +59,8 @@ export default class HostSetSerializer extends ApplicationSerializer { return serialized; } - serializeAttribute(snapshot, json, key, attribute) { + serializeAttribute(snapshot, json, key) { const value = super.serializeAttribute(...arguments); - const { isPlugin, compositeType } = snapshot.record; - const { options } = attribute; - - if (isPlugin && options.isNestedAttribute) { - if (!fieldByType[compositeType].includes(key)) { - delete json.attributes[key]; - } - } if (key === 'filter_string') { const { filter_string } = json.attributes; diff --git a/addons/api/addon/utils/flatten-nested-object.js b/addons/api/addon/utils/flatten-nested-object.js new file mode 100644 index 0000000000..5b62befccf --- /dev/null +++ b/addons/api/addon/utils/flatten-nested-object.js @@ -0,0 +1,27 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ +import { typeOf } from '@ember/utils'; + +/** + * Flattens a nested object into a single-level object. + * @param obj + * @param result + * @param parentKey + * @returns {Object} + */ + +export const flattenObject = (obj, result = {}, parentKey = '') => { + for (const key in obj) { + // Check if the value is an object and it is not null + if (typeOf(obj[key]) === 'object' && obj[key] !== null) { + // Recursively flatten the object + flattenObject(obj[key], result, `${parentKey}${key}.`); + } else if (obj[key]) { + // Only add to result if the value is not null + result[`${parentKey}${key}`] = obj[key]; + } + } + return result; +}; diff --git a/addons/api/mirage/factories/host-catalog.js b/addons/api/mirage/factories/host-catalog.js index 1d394004dc..910e8bb3c5 100644 --- a/addons/api/mirage/factories/host-catalog.js +++ b/addons/api/mirage/factories/host-catalog.js @@ -8,11 +8,23 @@ import { trait } from 'miragejs'; import permissions from '../helpers/permissions'; import { faker } from '@faker-js/faker'; import generateId from '../helpers/id'; +import { + TYPE_HOST_CATALOG_PLUGIN_AZURE, + TYPE_HOST_CATALOG_PLUGIN_GCP, + TYPE_HOST_CATALOG_PLUGIN_AWS, + TYPE_HOST_CATALOG_STATIC, + TYPE_HOST_CATALOG_DYNAMIC, + TYPES_HOST_CATALOG, +} from 'api/models/host-catalog'; -const types = ['static', 'plugin']; // Represents known plugin types, except "foobar" which models the possibility // of receiving an _unknown_ type, which the UI must handle gracefully. -const pluginTypes = ['aws', 'azure', 'foobar']; +const pluginTypes = [ + TYPE_HOST_CATALOG_PLUGIN_GCP, + TYPE_HOST_CATALOG_PLUGIN_AWS, + TYPE_HOST_CATALOG_PLUGIN_AZURE, + 'foobar', +]; let pluginTypeCounter = 1; @@ -20,7 +32,7 @@ export default factory.extend({ id: () => generateId('hc_'), // Cycle through available types - type: (i) => types[i % types.length], + type: (i) => TYPES_HOST_CATALOG[i % TYPES_HOST_CATALOG.length], authorized_actions: () => permissions.authorizedActionsFor('host-catalog') || [ @@ -30,7 +42,7 @@ export default factory.extend({ 'delete', ], authorized_collection_actions: function () { - const isStatic = this.type === 'static'; + const isStatic = this.type === TYPE_HOST_CATALOG_STATIC; return { // only static catalogs allow host create at this time hosts: isStatic ? ['create', 'list'] : ['list'], @@ -39,13 +51,17 @@ export default factory.extend({ }, worker_filter: function () { - if (this.type === 'plugin' && this.plugin?.name === 'aws') { + if ( + this.type === TYPE_HOST_CATALOG_DYNAMIC && + (this.plugin?.name === TYPE_HOST_CATALOG_PLUGIN_AWS || + this.plugin?.name === TYPE_HOST_CATALOG_PLUGIN_GCP) + ) { return `"${faker.word.noun()}" in "${faker.system.directoryPath()}"`; } }, plugin: function (i) { - if (this.type === 'plugin') { + if (this.type === TYPE_HOST_CATALOG_DYNAMIC) { return { id: `plugin-id-${i}`, name: pluginTypes[pluginTypeCounter++ % pluginTypes.length], @@ -56,35 +72,50 @@ export default factory.extend({ attributes() { switch (this.plugin?.name) { - case 'aws': + case TYPE_HOST_CATALOG_PLUGIN_AWS: return { disable_credential_rotation: faker.datatype.boolean(), region: `us-${faker.location.cardinalDirection()}-${faker.number.int( 9, )}`, }; - case 'azure': + case TYPE_HOST_CATALOG_PLUGIN_AZURE: return { disable_credential_rotation: faker.datatype.boolean(), tenant_id: faker.string.uuid(), client_id: faker.string.uuid(), subscription_id: faker.string.uuid(), }; + case TYPE_HOST_CATALOG_PLUGIN_GCP: + return { + disable_credential_rotation: faker.datatype.boolean(), + project_id: faker.string.uuid(), + client_email: faker.internet.email(), + target_service_account_id: faker.string.uuid(), + zone: `us-${faker.location.cardinalDirection()}${faker.number.int( + 9, + )}-${faker.string.fromCharacters('abc')}`, + }; } }, secrets() { switch (this.plugin?.name) { - case 'aws': + case TYPE_HOST_CATALOG_PLUGIN_AWS: return { access_key_id: faker.string.nanoid(), secret_access_key: faker.string.nanoid(), }; - case 'azure': + case TYPE_HOST_CATALOG_PLUGIN_AZURE: return { secret_id: faker.string.nanoid(), secret_value: faker.string.nanoid(), }; + case TYPE_HOST_CATALOG_PLUGIN_GCP: + return { + private_key: faker.string.nanoid(), + private_key_id: faker.string.nanoid(), + }; } }, withChildren: trait({ diff --git a/addons/api/mirage/factories/host-set.js b/addons/api/mirage/factories/host-set.js index eb5d8b30b8..8442d6dd50 100644 --- a/addons/api/mirage/factories/host-set.js +++ b/addons/api/mirage/factories/host-set.js @@ -7,6 +7,22 @@ import factory from '../generated/factories/host-set'; import permissions from '../helpers/permissions'; import { faker } from '@faker-js/faker'; import generateId from '../helpers/id'; +import { + TYPE_HOST_CATALOG_PLUGIN_AWS, + TYPE_HOST_CATALOG_PLUGIN_AZURE, + TYPE_HOST_CATALOG_PLUGIN_GCP, +} from 'api/models/host-catalog'; + +function generateFilters(prefix) { + const filtersAmount = faker.number.int({ min: 1, max: 5 }); + let filters = []; + for (let i = 0; i < filtersAmount; ++i) { + filters.push(`${prefix}${faker.word.words(1)}=${faker.word.words(1)}`); + } + return { + filters, + }; +} export default factory.extend({ id: () => generateId('hs_'), @@ -42,22 +58,15 @@ export default factory.extend({ }, attributes() { - // AWS specific - if (this.plugin?.name === 'aws') { - const filtersAmount = faker.number.int({ min: 1, max: 5 }); - let filters = []; - for (let i = 0; i < filtersAmount; ++i) { - filters.push(`${faker.word.words(1)}=${faker.word.words(1)}`); - } - return { - filters, - }; - } - // Azure specific - if (this.plugin?.name === 'azure') { - return { - filter: `${faker.database.column()}=${faker.database.collation()}`, - }; + switch (this.plugin?.name) { + case TYPE_HOST_CATALOG_PLUGIN_AWS: + return generateFilters('tag:'); + case TYPE_HOST_CATALOG_PLUGIN_AZURE: + return { + filter: `${faker.database.column()}=${faker.database.collation()}`, + }; + case TYPE_HOST_CATALOG_PLUGIN_GCP: + return generateFilters('labels.'); } }, }); diff --git a/addons/api/mirage/factories/host.js b/addons/api/mirage/factories/host.js index 515f55f517..64d86ac604 100644 --- a/addons/api/mirage/factories/host.js +++ b/addons/api/mirage/factories/host.js @@ -7,24 +7,28 @@ import factory from '../generated/factories/host'; import permissions from '../helpers/permissions'; import { faker } from '@faker-js/faker'; import generateId from '../helpers/id'; +import { + TYPE_HOST_CATALOG_STATIC, + TYPE_HOST_CATALOG_DYNAMIC, +} from 'api/models/host-catalog'; export default factory.extend({ id: () => generateId('h_'), name() { - if (this.type === 'static') return faker.word.words(); + if (this.type === TYPE_HOST_CATALOG_STATIC) return faker.word.words(); }, type() { return this.hostCatalog?.type || factory.attrs.type; }, authorized_actions: function () { - const isStatic = this.type === 'static'; + const isStatic = this.type === TYPE_HOST_CATALOG_STATIC; const defaults = ['no-op', 'read']; // Only static allows update/delete at this time. if (isStatic) defaults.push('update', 'delete'); return permissions.authorizedActionsFor('host') || defaults; }, plugin() { - if (this.type === 'plugin') { + if (this.type === TYPE_HOST_CATALOG_DYNAMIC) { return this.hostCatalog.plugin; } }, @@ -38,12 +42,12 @@ export default factory.extend({ }, // Return external fields only for plugins external_id() { - if (this.type === 'plugin') { + if (this.type === TYPE_HOST_CATALOG_DYNAMIC) { return faker.string.uuid(); } }, external_name(i) { - if (this.type === 'plugin') { + if (this.type === TYPE_HOST_CATALOG_DYNAMIC) { return `${this.plugin.name} provided name ${i}`; } }, diff --git a/addons/api/mirage/factories/scope.js b/addons/api/mirage/factories/scope.js index 35484166e7..edab1920ff 100644 --- a/addons/api/mirage/factories/scope.js +++ b/addons/api/mirage/factories/scope.js @@ -15,7 +15,7 @@ export default factory.extend({ authorized_actions() { const authorizedActions = ['no-op', 'read', 'update']; - // Storage policies can only be attatched to global and org scopes. + // Storage policies can only be attached to global and org scopes. if (this.type === 'global' || this.type === 'org') { authorizedActions.push('attach-storage-policy', 'detach-storage-policy'); } diff --git a/addons/api/mirage/route-handlers/auth.js b/addons/api/mirage/route-handlers/auth.js index eaa1f65736..66df837ab4 100644 --- a/addons/api/mirage/route-handlers/auth.js +++ b/addons/api/mirage/route-handlers/auth.js @@ -4,7 +4,6 @@ */ // import config from '../../config/environment'; -// import { v1 } from 'ember-uuid'; import { Response } from 'miragejs'; // /** diff --git a/addons/api/mirage/scenarios/ipc.js b/addons/api/mirage/scenarios/ipc.js index 62e17c9c54..bbc285ac08 100644 --- a/addons/api/mirage/scenarios/ipc.js +++ b/addons/api/mirage/scenarios/ipc.js @@ -83,6 +83,37 @@ export default function initializeMockIPC(server, config) { raw: btoa('just-a-random-string'), }, }, + { + credential_source: { + id: 'clvlt_4cvscMTl0N', + name: 'Credential Library 0', + description: 'Source Description', + credential_store_id: 'csvlt_Q1HFGt7Jpm', + type: 'vault-generic', + }, + secret: { + raw: 'eyJhcnJheSI6WyJvbmUiLCJ0d28iLCJ0aHJlZSIsIm9uZSIsInR3byIsInRocmVlIiwib25lIiwidHdvIiwidGhyZWUiLCJvbmUiLCJ0d28iLCJ0aHJlZSJdLCJuZXN0ZWQiOnsiYm9vbCI6dHJ1ZSwibG9uZyI6IjEyMjM1MzQ1NmFzZWRmYTQzd3J0ZjIzNGYyM2FzZGdmYXNkZnJnYXdzZWZhd3NlZnNkZjQiLCJzZWNlcmV0Ijoic28gbmVzdGVkIn0sInRlc3QiOiJwaHJhc2UifQ', + decoded: { + data: { + backslash: 'password\\withslash\\', + backslash1: 'password\\fwithslash\\f', + password: '123', + username: 'test', + email: { + address: 'test.com', + }, + }, + metadata: { + created_time: '2024-04-12T18:38:36.226715555Z', + custom_metadata: null, + deletion_time: '', + destroyed: false, + version: 8, + }, + }, + }, + }, + { credential_source: { id: 'credjson_7cKBbBEkC3', @@ -98,6 +129,7 @@ export default function initializeMockIPC(server, config) { nested_secret: { session_token: 'ZXCVBNMLKJHGFDSAQWERTYUIOP0987654321', complex_nest: { + blackslash: 'password\\withslash\\', hash: 'qazxswedcvfrtgbnjhyujm.1234567890', }, }, diff --git a/addons/api/package.json b/addons/api/package.json index 7e1d8b4f7a..d3a0790c62 100644 --- a/addons/api/package.json +++ b/addons/api/package.json @@ -72,9 +72,6 @@ "qunit-dom": "^3.2.1", "webpack": "^5.94.0" }, - "peerDependencies": { - "ember-source": ">4.0.0" - }, "engines": { "node": "20.* || 22.*" }, diff --git a/addons/api/tests/unit/models/host-catalog-test.js b/addons/api/tests/unit/models/host-catalog-test.js index 73094e9e36..1cf37e765e 100644 --- a/addons/api/tests/unit/models/host-catalog-test.js +++ b/addons/api/tests/unit/models/host-catalog-test.js @@ -81,6 +81,19 @@ module('Unit | Model | host catalog', function (hooks) { assert.false(modelRandom.isAzure); }); + test('it has isGCP property and return the expected values', async function (assert) { + const store = this.owner.lookup('service:store'); + const modelGCP = store.createRecord('host-catalog', { + type: 'plugin', + plugin: { name: 'gcp' }, + }); + const modelRandom = store.createRecord('host-catalog', { + plugin: { name: 'random' }, + }); + assert.true(modelGCP.isGCP); + assert.false(modelRandom.isGCP); + }); + test('get compositeType returns expected values', async function (assert) { const store = this.owner.lookup('service:store'); const modelA = store.createRecord('host-catalog', { @@ -94,8 +107,13 @@ module('Unit | Model | host catalog', function (hooks) { type: 'plugin', plugin: { name: 'no-such-type' }, }); + const modelD = store.createRecord('host-catalog', { + type: 'plugin', + plugin: { name: 'gcp' }, + }); assert.strictEqual(modelA.compositeType, 'static'); assert.strictEqual(modelB.compositeType, 'aws'); + assert.strictEqual(modelD.compositeType, 'gcp'); assert.strictEqual(modelC.compositeType, 'unknown'); }); @@ -107,8 +125,12 @@ module('Unit | Model | host catalog', function (hooks) { const modelStatic = store.createRecord('host-catalog', { compositeType: 'static', }); + const modelGCP = store.createRecord('host-catalog', { + compositeType: 'gcp', + }); assert.strictEqual(modelPlugin.type, 'plugin'); assert.strictEqual(modelPlugin.plugin.name, 'aws'); assert.strictEqual(modelStatic.type, 'static'); + assert.strictEqual(modelGCP.plugin.name, 'gcp'); }); }); diff --git a/addons/api/tests/unit/models/session-test.js b/addons/api/tests/unit/models/session-test.js index dfd4319178..f34e6ee80a 100644 --- a/addons/api/tests/unit/models/session-test.js +++ b/addons/api/tests/unit/models/session-test.js @@ -5,6 +5,8 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; +import { SessionCredential } from 'api/models/session'; +import { TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC } from 'api/models/credential-library'; module('Unit | Model | session', function (hooks) { setupTest(hooks); @@ -16,6 +18,46 @@ module('Unit | Model | session', function (hooks) { assert.notOk(model.isAvailable); }); + test('`extractSecrets` method should return an array of secret items', function (assert) { + const sessionCredential = new SessionCredential({ + credential_source: { + id: '123', + name: 'Test Source', + description: 'Test Description', + type: 'Test Type', + }, + }); + + const secrets = { + username: 'user', + password: 'pass', + }; + + const formattedSecretes = sessionCredential.extractSecrets( + secrets, + TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC, + ); + assert.deepEqual(formattedSecretes, [ + new SessionCredential.SecretItem('username', 'user'), + new SessionCredential.SecretItem('password', 'pass'), + ]); + + // check for nested data, empty values should be filtered out + const nestedSecrets = { + data: { username: 'user', password: '', email: 'test.com' }, + }; + + const formattedNestedSecrets = sessionCredential.extractSecrets( + nestedSecrets, + TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC, + ); + + assert.deepEqual(formattedNestedSecrets, [ + new SessionCredential.SecretItem('username', 'user'), + new SessionCredential.SecretItem('email', 'test.com'), + ]); + }); + test('it allows cancellation of an active session', function (assert) { let store = this.owner.lookup('service:store'); let model = store.createRecord('session', { diff --git a/addons/api/tests/unit/serializers/application-test.js b/addons/api/tests/unit/serializers/application-test.js index c2eb20e211..0d2ae34f44 100644 --- a/addons/api/tests/unit/serializers/application-test.js +++ b/addons/api/tests/unit/serializers/application-test.js @@ -6,6 +6,7 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; import { TYPE_AUTH_METHOD_OIDC } from 'api/models/auth-method'; +import { TYPE_HOST_CATALOG_PLUGIN_GCP } from 'api/models/host-catalog'; module('Unit | Serializer | application', function (hooks) { setupTest(hooks); @@ -133,7 +134,7 @@ module('Unit | Serializer | application', function (hooks) { }); }); - test('it serializes attributes with `for` option containing array or string correctly', function (assert) { + test('it serializes attributes with `for` option containing array or string correctly for non plugins', function (assert) { let store = this.owner.lookup('service:store'); let record = store.createRecord('auth-method', { type: TYPE_AUTH_METHOD_OIDC, //has both string and array `for` option @@ -184,6 +185,54 @@ module('Unit | Serializer | application', function (hooks) { }); }); + test('it serializes attributes with `for` option containing array or string correctly for plugin types', function (assert) { + let store = this.owner.lookup('service:store'); + let record = store.createRecord('host-catalog', { + compositeType: TYPE_HOST_CATALOG_PLUGIN_GCP, + name: 'GCP plugin', + description: 'this is a GCP plugin host-catalog', + disable_credential_rotation: true, + worker_filter: 'workerfilter', + access_key_id: 'foobars', + secret_access_key: 'testing', + secret_id: 'a1b2c3', + secret_value: 'a1b2c3', + role_arn: 'test', + role_external_id: 'test', + role_session_name: 'test', + role_tags: [ + { key: 'Project', value: 'Automation' }, + { key: 'foo', value: 'bar' }, + ], + project_id: 'project', + zone: 'zone', + client_email: 'email', + target_service_account_id: 'service-account', + private_key_id: 'key-id', + private_key: 'key', + }); + + let serializedRecord = record.serialize(); + const expectedResult = { + name: 'GCP plugin', + description: 'this is a GCP plugin host-catalog', + type: 'plugin', + worker_filter: 'workerfilter', + attributes: { + disable_credential_rotation: true, + project_id: 'project', + zone: 'zone', + target_service_account_id: 'service-account', + client_email: 'email', + }, + secrets: { + private_key_id: 'key-id', + private_key: 'key', + }, + }; + assert.deepEqual(serializedRecord, expectedResult); + }); + test('it normalizes array records from an `items` root key', function (assert) { const store = this.owner.lookup('service:store'); const serializer = store.serializerFor('user'); diff --git a/addons/api/tests/unit/serializers/host-catalog-test.js b/addons/api/tests/unit/serializers/host-catalog-test.js index 45b6ef55f6..e1aa519f00 100644 --- a/addons/api/tests/unit/serializers/host-catalog-test.js +++ b/addons/api/tests/unit/serializers/host-catalog-test.js @@ -9,6 +9,7 @@ import { TYPE_HOST_CATALOG_PLUGIN_AWS, TYPE_CREDENTIAL_STATIC, TYPE_CREDENTIAL_DYNAMIC, + TYPE_HOST_CATALOG_PLUGIN_GCP, } from 'api/models/host-catalog'; module('Unit | Serializer | host catalog', function (hooks) { @@ -32,6 +33,53 @@ module('Unit | Serializer | host catalog', function (hooks) { name: 'static', description: 'this is a static host-catalog', type: 'static', + attributes: {}, + }; + assert.deepEqual(record.serialize(), expectedResult); + }); + + test('it serializes a new gcp plugin as expected', async function (assert) { + const store = this.owner.lookup('service:store'); + const record = store.createRecord('host-catalog', { + compositeType: TYPE_HOST_CATALOG_PLUGIN_GCP, + name: 'GCP', + description: 'this is a GCP plugin host-catalog', + disable_credential_rotation: true, + worker_filter: 'workerfilter', + access_key_id: 'foobars', + secret_access_key: 'testing', + secret_id: 'a1b2c3', + secret_value: 'a1b2c3', + role_arn: 'test', + role_external_id: 'test', + role_session_name: 'test', + role_tags: [ + { key: 'Project', value: 'Automation' }, + { key: 'foo', value: 'bar' }, + ], + project_id: 'project', + zone: 'zone', + client_email: 'email', + target_service_account_id: 'service-account', + private_key_id: 'key-id', + private_key: 'key', + }); + const expectedResult = { + name: 'GCP', + description: 'this is a GCP plugin host-catalog', + type: 'plugin', + worker_filter: 'workerfilter', + attributes: { + disable_credential_rotation: true, + project_id: 'project', + zone: 'zone', + target_service_account_id: 'service-account', + client_email: 'email', + }, + secrets: { + private_key_id: 'key-id', + private_key: 'key', + }, }; assert.deepEqual(record.serialize(), expectedResult); }); @@ -79,7 +127,7 @@ module('Unit | Serializer | host catalog', function (hooks) { assert.deepEqual(record.serialize(), expectedResult); }); - test('it serializes a new aws dynamic plugin as expected', async function (assert) { + test('it serializes a new aws dynamic credential type plugin as expected', async function (assert) { const store = this.owner.lookup('service:store'); const record = store.createRecord('host-catalog', { compositeType: TYPE_HOST_CATALOG_PLUGIN_AWS, @@ -88,7 +136,7 @@ module('Unit | Serializer | host catalog', function (hooks) { description: 'this is a Aws plugin host-catalog', disable_credential_rotation: true, worker_filter: 'workerfilter', - // these are AWS fields and should be included + // these are static AWS fields and should be excluded region: 'west', access_key_id: 'foobars', secret_access_key: 'testing', @@ -123,7 +171,7 @@ module('Unit | Serializer | host catalog', function (hooks) { assert.deepEqual(record.serialize(), expectedResult); }); - test('it serializes a new aws static plugin as expected, ignoring azure fields', async function (assert) { + test('it serializes a new aws static credential type plugin as expected, ignoring azure and GCP fields', async function (assert) { const store = this.owner.lookup('service:store'); const record = store.createRecord('host-catalog', { compositeType: TYPE_HOST_CATALOG_PLUGIN_AWS, @@ -142,6 +190,8 @@ module('Unit | Serializer | host catalog', function (hooks) { subscription_id: 'a1b2c3', secret_id: 'a1b2c3', secret_value: 'a1b2c3', + project_id: 'project', + zone: 'zone', }); const expectedResult = { name: 'AWS', @@ -164,7 +214,7 @@ module('Unit | Serializer | host catalog', function (hooks) { assert.deepEqual(record.serialize(), expectedResult); }); - test('it serializes a new azure record as expected, ignoring aws fields', async function (assert) { + test('it serializes a new azure record as expected, ignoring aws and GCP fields', async function (assert) { const store = this.owner.lookup('service:store'); const record = store.createRecord('host-catalog', { compositeType: 'azure', @@ -181,6 +231,8 @@ module('Unit | Serializer | host catalog', function (hooks) { subscription_id: 'a1b2c3', secret_id: 'a1b2c3', secret_value: 'a1b2c3', + project_id: 'project', + zone: 'zone', }); const expectedResult = { name: 'Azure', @@ -271,7 +323,7 @@ module('Unit | Serializer | host catalog', function (hooks) { assert.deepEqual(record.serialize(), expectedResult); }); - test('it normalizes an static host catalog as expected', function (assert) { + test('it normalizes a static host catalog as expected', function (assert) { const store = this.owner.lookup('service:store'); const serializer = store.serializerFor('host-catalog'); const hostCatalog = store.createRecord('host-catalog').constructor; @@ -300,6 +352,8 @@ module('Unit | Serializer | host catalog', function (hooks) { secret_access_key: null, secret_id: null, secret_value: null, + private_key_id: null, + private_key: null, }, relationships: {}, }, @@ -358,6 +412,8 @@ module('Unit | Serializer | host catalog', function (hooks) { secret_access_key: 'zq{2:IVc8@W^', secret_id: null, secret_value: null, + private_key_id: null, + private_key: null, }, relationships: {}, }, @@ -418,6 +474,70 @@ module('Unit | Serializer | host catalog', function (hooks) { subscription_id: 'subscription', access_key_id: null, secret_access_key: null, + private_key_id: null, + private_key: null, + }, + relationships: {}, + }, + included: [], + }); + }); + + test('it normalizes a GCP plugin type host catalog as expected', function (assert) { + const store = this.owner.lookup('service:store'); + const serializer = store.serializerFor('host-catalog'); + const hostCatalog = store.createRecord('host-catalog').constructor; + const payload = { + id: '1', + name: 'Host catalog test', + description: 'Test description', + authorized_actions: ['no-op', 'read', 'update', 'delete'], + type: 'plugin', + plugin: { + id: 'plugin-id-7', + name: 'gcp', + description: 'gcp host catalog', + }, + attributes: { + disable_credential_rotation: true, + project_id: 'project_id', + zone: 'zone', + target_service_account_id: 'target_service_account_id', + }, + secrets: { + private_key_id: '0xF3B0a6f8', + private_key: 'zq{2:IVc8@W^', + }, + }; + const normalized = serializer.normalizeSingleResponse( + store, + hostCatalog, + payload, + ); + assert.deepEqual(normalized, { + data: { + id: '1', + type: 'host-catalog', + attributes: { + name: 'Host catalog test', + type: 'plugin', + description: 'Test description', + authorized_actions: ['no-op', 'read', 'update', 'delete'], + plugin: { + id: 'plugin-id-7', + name: 'gcp', + description: 'gcp host catalog', + }, + disable_credential_rotation: true, + private_key_id: '0xF3B0a6f8', + private_key: 'zq{2:IVc8@W^', + project_id: 'project_id', + zone: 'zone', + target_service_account_id: 'target_service_account_id', + access_key_id: null, + secret_access_key: null, + secret_id: null, + secret_value: null, }, relationships: {}, }, diff --git a/addons/api/tests/unit/services/indexed-db-test.js b/addons/api/tests/unit/services/indexed-db-test.js index 710e18b956..ffd3d62c68 100644 --- a/addons/api/tests/unit/services/indexed-db-test.js +++ b/addons/api/tests/unit/services/indexed-db-test.js @@ -12,7 +12,6 @@ class SessionService extends Service {} module('Unit | Service | indexed-db', function (hooks) { setupTest(hooks); - // TODO: Replace this with your real tests. test('it exists', function (assert) { this.owner.register('service:session', SessionService); diff --git a/addons/api/tests/unit/services/store-test.js b/addons/api/tests/unit/services/store-test.js index c7ee79d325..376db814d1 100644 --- a/addons/api/tests/unit/services/store-test.js +++ b/addons/api/tests/unit/services/store-test.js @@ -9,7 +9,6 @@ import { setupTest } from 'dummy/tests/helpers'; module('Unit | Service | store', function (hooks) { setupTest(hooks); - // TODO: Replace this with your real tests. test('it exists', function (assert) { let service = this.owner.lookup('service:store'); assert.ok(service); diff --git a/addons/api/tests/unit/utils/flatten-nested-object-test.js b/addons/api/tests/unit/utils/flatten-nested-object-test.js new file mode 100644 index 0000000000..0a5b00b5df --- /dev/null +++ b/addons/api/tests/unit/utils/flatten-nested-object-test.js @@ -0,0 +1,43 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { flattenObject } from 'api/utils/flatten-nested-object'; +import { module, test } from 'qunit'; + +module('Unit | Utility | flatten-nested-object', function () { + test('it flattens a nested object to single level object', function (assert) { + const input = { + a: 1, + b: { c: 2, d: 3, e: { f: { g: { h: 10 } } } }, + }; + const expected = { + a: 1, + 'b.c': 2, + 'b.d': 3, + 'b.e.f.g.h': 10, + }; + assert.deepEqual(flattenObject(input), expected); + }); + + test('removes keys with null/empty values', function (assert) { + const inputOne = { + a: null, + b: { c: 2 }, + d: { + e: { f: { g: { h: '' } } }, + }, + }; + const inputTwo = { + d: { e: null }, + }; + const expectedResultOne = { + 'b.c': 2, + }; + const expectedResultTwo = {}; + + assert.deepEqual(flattenObject(inputOne), expectedResultOne); + assert.deepEqual(flattenObject(inputTwo), expectedResultTwo); + }); +}); diff --git a/addons/auth/addon/authenticators/base.js b/addons/auth/addon/authenticators/base.js index a3cdafabd8..a48aaaeaca 100644 --- a/addons/auth/addon/authenticators/base.js +++ b/addons/auth/addon/authenticators/base.js @@ -58,6 +58,7 @@ export default class BaseAuthenticator extends SimpleAuthBaseAuthenticator { */ async validateToken(token, tokenID) { const tokenValidationURL = this.buildTokenValidationEndpointURL(tokenID); + // Note: waitForPromise is needed to provide the necessary integration with @ember/test-helpers // visit https://www.npmjs.com/package/@ember/test-waiters for more info. const response = await waitForPromise( @@ -66,6 +67,12 @@ export default class BaseAuthenticator extends SimpleAuthBaseAuthenticator { headers: { Authorization: `Bearer ${token}` }, }), ); + // Note: Always consume response body in order to avoid memory leaks + // visit https://undici.nodejs.org/#/?id=garbage-collection for more info. + // We do not use the undici package but the link informs us that garbage + // collection is undefined when response body is not consumed. + await response.json(); + // 401 and 404 responses mean the token is invalid, whereas other types of // error responses do not tell us about the validity of the token. if (response.status === 401 || response.status === 404) return reject(); diff --git a/addons/auth/addon/authenticators/oidc.js b/addons/auth/addon/authenticators/oidc.js index dc1063d5e4..6b38c6c050 100644 --- a/addons/auth/addon/authenticators/oidc.js +++ b/addons/auth/addon/authenticators/oidc.js @@ -9,7 +9,7 @@ import { reject } from 'rsvp'; import { waitForPromise } from '@ember/test-waiters'; /** - * The OIDC base authenticator encapsulates the multistep OIDC flow. + * The OIDC base authenticator encapsulates the multi-step OIDC flow. * * 1. Start authentication flow: this step is actually a combination of two * sub steps: @@ -97,13 +97,19 @@ export default class OIDCAuthenticator extends BaseAuthenticator { }); // Fetch the endpoint and get the response JSON const response = await waitForPromise(fetch(url, { method: 'post', body })); + + // Note: Always consume response body in order to avoid memory leaks + // visit https://undici.nodejs.org/#/?id=garbage-collection for more info. + // We do not use the undici package but the link informs us that garbage + // collection is undefined when response body is not consumed. + const json = await response.json(); + if (response.status === 202) { // The token isn't ready yet, keep trying. return false; } else if (response.status < 400) { // Response was successful, meaning a token was obtained. // Authenticate with the session service using the response JSON. - const json = await response.json(); await this.session.authenticate('authenticator:oidc', json); return true; } else { diff --git a/addons/auth/package.json b/addons/auth/package.json index 727c2c86b0..e0397fbe95 100644 --- a/addons/auth/package.json +++ b/addons/auth/package.json @@ -27,6 +27,7 @@ "ember-simple-auth": "^6.1.0" }, "devDependencies": { + "@babel/core": "^7.25.2", "@babel/eslint-parser": "^7.21.3", "@babel/plugin-proposal-decorators": "^7.21.0", "@ember/optional-features": "^2.0.0", diff --git a/addons/auth/tests/unit/authenticators/base-test.js b/addons/auth/tests/unit/authenticators/base-test.js index 6bcf988f6c..3a7f2f38c6 100644 --- a/addons/auth/tests/unit/authenticators/base-test.js +++ b/addons/auth/tests/unit/authenticators/base-test.js @@ -52,7 +52,7 @@ module('Unit | Authenticator | base', function (hooks) { const authenticator = this.owner.lookup('authenticator:base'); server.get(authenticator.buildTokenValidationEndpointURL(id), () => { assert.ok(true, 'token validation was requested'); - return [200]; + return [200, {}, '{}']; }); await authenticator.restore(mockData); }); @@ -64,7 +64,7 @@ module('Unit | Authenticator | base', function (hooks) { const authenticator = this.owner.lookup('authenticator:base'); server.get(authenticator.buildTokenValidationEndpointURL(id), () => { assert.ok(true, 'token validation was requested'); - return [401]; + return [401, {}, '{}']; }); try { await authenticator.restore(mockData); @@ -80,7 +80,7 @@ module('Unit | Authenticator | base', function (hooks) { const authenticator = this.owner.lookup('authenticator:base'); server.get(authenticator.buildTokenValidationEndpointURL(id), () => { assert.ok(true, 'token validation was requested'); - return [404]; + return [404, {}, '{}']; }); try { await authenticator.restore(mockData); diff --git a/addons/core/addon/components/error/message/index.hbs b/addons/core/addon/components/error/message/index.hbs index 860fa3e9d3..ff045b6258 100644 --- a/addons/core/addon/components/error/message/index.hbs +++ b/addons/core/addon/components/error/message/index.hbs @@ -3,16 +3,21 @@ SPDX-License-Identifier: BUSL-1.1 }} - - - {{t (concat 'errors.' this.messageCode '.description')}} - - {{#if this.helpRoute}} - {{t 'actions.help'}} + + + + {{#if @docLink}} + + + {{/if}} - \ No newline at end of file + \ No newline at end of file diff --git a/addons/core/addon/components/error/message/index.js b/addons/core/addon/components/error/message/index.js index 07a6cea1ec..f76f3c350f 100644 --- a/addons/core/addon/components/error/message/index.js +++ b/addons/core/addon/components/error/message/index.js @@ -3,8 +3,7 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import MessageComponent from 'rose/components/rose/message'; -import { computed } from '@ember/object'; +import Component from '@glimmer/component'; /* * Helpful error booleans are attached based on the error status code: @@ -22,40 +21,29 @@ import { computed } from '@ember/object'; const statuses = ['401', '403', '404', '500']; -export default class ErrorMessageComponent extends MessageComponent { +export default class ErrorMessageComponent extends Component { // =methods /** * Returns an icon for error status. * @return {string} */ - @computed('args.status') get icon() { switch (this.args.status) { case '404': - return 'flight-icons/svg/help-16'; + return 'help'; case '401': case '403': default: - return 'flight-icons/svg/alert-circle-16'; + return 'alert-circle'; } } - // TODO: Enable when help documentation can be linked in. - /** - * Returns an route string for help text in message. - * @return {string} - */ - // get helpRoute() { - // return 'index' - // } - /** * Returns 'unknown' status code when provided error status * isn't part of predefined statuses. * @return {string} */ - @computed('args.status') get messageCode() { let messageCode = this.args.status; if (!statuses.includes(messageCode)) messageCode = 'unknown'; diff --git a/addons/core/addon/styles/addon.scss b/addons/core/addon/styles/addon.scss index 891f49b8e6..2a9218e05d 100644 --- a/addons/core/addon/styles/addon.scss +++ b/addons/core/addon/styles/addon.scss @@ -65,3 +65,10 @@ overflow: hidden; } } + +// Utility classes +.container-inputs__margin-bottom { + input:not(:last-child) { + margin-bottom: 0.5rem; + } +} diff --git a/addons/core/package.json b/addons/core/package.json index d9f6a3f259..966feb60c4 100644 --- a/addons/core/package.json +++ b/addons/core/package.json @@ -44,6 +44,7 @@ "ember-loading": "^2.0.0", "ember-truth-helpers": "^3.0.0", "filesize": "^10.0.7", + "lodash": "^4.17.21", "sass": "^1.69.5" }, "devDependencies": { @@ -84,13 +85,9 @@ "qunit": "^2.22.0", "qunit-dom": "^3.2.1", "rose": "*", - "sinon": "^18.0.0", + "sinon": "^19.0.2", "webpack": "^5.94.0" }, - "peerDependencies": { - "ember-source": "^4.0.0", - "lodash": "^4.17.21" - }, "engines": { "node": "20.* || 22.*" }, diff --git a/addons/core/tests/integration/components/error/message-test.js b/addons/core/tests/integration/components/error/message-test.js index f157526470..2169a51907 100644 --- a/addons/core/tests/integration/components/error/message-test.js +++ b/addons/core/tests/integration/components/error/message-test.js @@ -5,7 +5,7 @@ import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; -import { render, find } from '@ember/test-helpers'; +import { render } from '@ember/test-helpers'; import { hbs } from 'ember-cli-htmlbars'; import { setupIntl } from 'ember-intl/test-support'; @@ -13,61 +13,65 @@ module('Integration | Component | error/message', function (hooks) { setupRenderingTest(hooks); setupIntl(hooks, 'en-us'); + const ERROR_MSG_TITLE = + '[data-test-error-application-state] .hds-application-state__title'; + const ERROR_MSG_CODE = + '[data-test-error-application-state] .hds-application-state__error-code'; + const ERROR_MSG_BODY = + '[data-test-error-application-state] .hds-application-state__body-text'; + const ERROR_MSG_LINK = + '[data-test-error-application-state] .hds-link-standalone'; + const ERROR_MSG_LINK_TEXT = + '[data-test-error-application-state] .hds-link-standalone__text'; + test('it renders predefined message for known error status', async function (assert) { await render(hbs``); - assert.strictEqual( - find('.rose-message-title').textContent.trim(), - 'You are not signed in', - ); - assert.strictEqual( - find('.rose-message-subtitle').textContent.trim(), - 'Error 401', - ); - assert.strictEqual( - find('.rose-message-description').textContent.trim(), - 'You are not signed in. Please sign in and try again later.', - ); + + assert.dom(ERROR_MSG_TITLE).hasText('You are not signed in'); + assert.dom(ERROR_MSG_CODE).hasText('ERROR 401'); + assert + .dom(ERROR_MSG_BODY) + .hasText('You are not signed in. Please sign in and try again later.'); + await render(hbs``); - assert.strictEqual( - find('.rose-message-title').textContent.trim(), - 'You are not authorized', - ); - assert.strictEqual( - find('.rose-message-subtitle').textContent.trim(), - 'Error 403', - ); - assert.strictEqual( - find('.rose-message-description').textContent.trim(), - 'You must be granted permissions to view this data. Ask your administrator if you think you should have access.', - ); + + assert.dom(ERROR_MSG_TITLE).hasText('You are not authorized'); + assert.dom(ERROR_MSG_CODE).hasText('ERROR 403'); + assert + .dom(ERROR_MSG_BODY) + .hasText( + 'You must be granted permissions to view this data. Ask your administrator if you think you should have access.', + ); + await render(hbs``); - assert.strictEqual( - find('.rose-message-title').textContent.trim(), - 'Resource not found', - ); - assert.strictEqual( - find('.rose-message-subtitle').textContent.trim(), - 'Error 404', - ); - assert.strictEqual( - find('.rose-message-description').textContent.trim(), - 'We could not find the requested resource. You can ask your administrator or try again later.', - ); + + assert.dom(ERROR_MSG_TITLE).hasText('Resource not found'); + assert.dom(ERROR_MSG_CODE).hasText('ERROR 404'); + assert + .dom(ERROR_MSG_BODY) + .hasText( + 'We could not find the requested resource. You can ask your administrator or try again later.', + ); }); test('it renders default error for unknown error status', async function (assert) { await render(hbs``); - assert.strictEqual( - find('.rose-message-title').textContent.trim(), - 'Something went wrong', - ); - assert.strictEqual( - find('.rose-message-subtitle').textContent.trim(), - 'Error 599', - ); - assert.strictEqual( - find('.rose-message-description').textContent.trim(), - "We're not sure what happened. Please contact your administrator or try again later.", + + assert.dom(ERROR_MSG_TITLE).hasText('Something went wrong'); + assert.dom(ERROR_MSG_CODE).hasText('ERROR 599'); + assert + .dom(ERROR_MSG_BODY) + .hasText( + "We're not sure what happened. Please contact your administrator or try again later.", + ); + }); + + test('it renders help link with provided doc url', async function (assert) { + await render( + hbs``, ); + + assert.dom(ERROR_MSG_LINK_TEXT).hasText('Need help'); + assert.dom(ERROR_MSG_LINK).hasAttribute('href', 'https://support'); }); }); diff --git a/addons/core/tests/unit/initializers/deprecations-test.js b/addons/core/tests/unit/initializers/deprecations-test.js index 1990dc4bea..75a8dd62b3 100644 --- a/addons/core/tests/unit/initializers/deprecations-test.js +++ b/addons/core/tests/unit/initializers/deprecations-test.js @@ -30,7 +30,6 @@ module('Unit | Initializer | deprecations', function (hooks) { run(this.application, 'destroy'); }); - // TODO: Replace this with your real tests. test('it works', async function (assert) { await this.application.boot(); diff --git a/addons/core/tests/unit/services/clock-tick-test.js b/addons/core/tests/unit/services/clock-tick-test.js index d8865e9bc0..c6ce23a0d5 100644 --- a/addons/core/tests/unit/services/clock-tick-test.js +++ b/addons/core/tests/unit/services/clock-tick-test.js @@ -9,7 +9,6 @@ import { setupTest } from 'ember-qunit'; module('Unit | Service | clock-tick', function (hooks) { setupTest(hooks); - // TODO: Replace this with your real tests. test('it exists', function (assert) { let service = this.owner.lookup('service:clock-tick'); assert.ok(service); diff --git a/addons/core/translations/actions/en-us.yaml b/addons/core/translations/actions/en-us.yaml index 961420913a..7ed7bcd097 100644 --- a/addons/core/translations/actions/en-us.yaml +++ b/addons/core/translations/actions/en-us.yaml @@ -15,7 +15,7 @@ confirm: Confirm done: Done ok: OK deny: Deny -help: Need help? +help: Need help next: Next back: Back view: View @@ -63,3 +63,5 @@ narrow-results: Narrow results overflow-options: Overflow Options show-errors: Show Errors hide-errors: Hide Errors +edit-worker-filter: Edit Worker Filter +add-worker-filter: Add Worker Filter diff --git a/addons/core/translations/en-us.yaml b/addons/core/translations/en-us.yaml index d398d28547..1850af1bf6 100644 --- a/addons/core/translations/en-us.yaml +++ b/addons/core/translations/en-us.yaml @@ -44,6 +44,8 @@ titles: not-set: Not set year: '{numberOfYears, plural, =1 {# year} other {# years}}' days: '{days, plural, =1 {# day} other {# days}}' + workers: Workers + no-worker-filter: No worker filter added descriptions: empty-set: There are no items to display yet. You may be able to add items or try back later. cluster-url-initialization: To get started, please enter your cluster URL @@ -133,3 +135,26 @@ settings: alerts: cache-daemon: There may be a problem with the cache daemon client-agent: There may be a problem with the client agent +worker-filter-generator: + title: Filter generator + description: Choose what you want to format into a filter. + link: Learn more about formatting a worker filter. + toggle: + title: Show filter generator + description: The generator will automatically generate a pre-formatted filter for you. You can copy the result to the editor above. + input-values: + title: Input values + description: Next, input what you want to format. + formatted-result: + title: Formatted result + description: Copy and paste this into the editor above. + label: Generated result + tag: + label: Tag + helper: Format a worker tag + name: + label: Name + helper: Format a worker name + operator: + matches: matches (regex) + contains: contains diff --git a/addons/core/translations/form/en-us.yaml b/addons/core/translations/form/en-us.yaml index 125d73e964..b5aea8ebf2 100644 --- a/addons/core/translations/form/en-us.yaml +++ b/addons/core/translations/form/en-us.yaml @@ -18,6 +18,8 @@ description: type: label: Type help: The type of the resource, to help differentiate schemas +plugin_type: + label: plugin_type login_name: label: Login Name help: The account login name @@ -134,6 +136,8 @@ key: label: Key value: label: Value +operator: + label: Operator callback_url: label: Callback URL help: The URL to which the third-party provider sends the authentication response. Paste this into the provider configuration. @@ -179,3 +183,5 @@ created: other_fields: label: Other Fields help: This information is provided by the server and cannot be edited in Boundary +worker_filter: + label: Worker Filter diff --git a/addons/core/translations/resources/en-us.yaml b/addons/core/translations/resources/en-us.yaml index c09d23d66a..ce8b0ed52b 100644 --- a/addons/core/translations/resources/en-us.yaml +++ b/addons/core/translations/resources/en-us.yaml @@ -220,6 +220,7 @@ host-catalog: aws: AWS azure: Azure plugin: Dynamic + gcp: GCP credential: label: Credential type help: Select the credential type that you want to use. @@ -239,8 +240,25 @@ host-catalog: label: Client/Application ID help: The client (application) ID of an Azure service principal that Boundary will use to authenticate and discover hosts from Azure. worker_filter: - label: Worker Filter help: Insert a worker filter in order to route requests to the correct worker. + project_id: + label: Project ID + help: Project ID of the instances you want to add to host catalog. + zone: + label: Zone + help: Zone of the instances you want to add to host catalog. + client_email: + label: Client Email + help: The email address used to uniquely identify the service account. + target_service_account_id: + label: Target Service Account ID + help: The unique identifier for the service account that will be impersonated. This is only used when authenticating with service account impersonation. + private_key_id: + label: Private Key ID + help: The unique identifier of private key. + private_key: + label: Private Key + help: The private key used to obtain a OAuth 2.0 access token. Key must be PEM encoded. host-set: title: Host Set title_plural: Host Sets @@ -257,9 +275,11 @@ host-set: filter: label: Filter aws: - help: Create a filter to select resources using values such as tag name or tag value. The AWS filter format is tag:=. For example:tag:application=production + help: 'Create a filter to select resources using values such as tag name or tag value. The AWS filter format is tag:=. For example: tag:application=production' azure: help: Create a filter to select resources using values such as tag name or tag value. The Azure filter format is:tagName eq ‘’ and tagValue eq ‘'. For example:tagName eq 'application' and tagValue eq 'production' + gcp: + help: 'Create a filter to select resources using values such as label name or label value. The GCP filter format is labels.=. For example: labels.application=production' preferred_endpoints: label: Preferred Endpoints help: Preferred address at which the host should be accessed when multiple options are present, in cidr: or dns: format. @@ -501,8 +521,7 @@ storage-bucket: label: Secret access key help: The secret access key generated by your storage provider for the IAM user to use with this storage bucket. We will not show this data after it is saved, but you can replace it. worker_filter: - label: Worker filter - help: Filter for Boundary workers that have network access to your storage. Boolean expression. + help: Filters to the worker(s) that can handle requests for this storage bucket. disable_credential_rotation: label: Disable credential rotation help: Disabling credential rotation could allow entities outside of Boundary to know the client secret in use. @@ -782,11 +801,9 @@ target: title: Add Injected Application Credential Sources description: Select injected Application credential sources to assign to this target. workers: - title: Workers description: You can customize how your workers route traffic to this target by setting up a filter that matches specific worker tags. If your target is in a private network, we recommend setting up an egress filter to specify a worker inside the network so that Boundary can access the host. Ingress and egress filters are allowed to select the same worker. messages: none: - title: No worker filter added description: You haven't added an {type} worker filter yet. diagram: client: Client @@ -812,13 +829,10 @@ target: title: Edit Ingress Worker Filter description: Specify workers Boundary should use to start the route to this target. worker-filter: - title: Worker filter description: The worker filter format for this target. accordion-label: egress-workers: Egress workers ingress-workers: Ingress workers - actions: - edit-worker-filter: Edit worker filter credential-store: title: Credential Store title_plural: Credential Stores @@ -828,6 +842,13 @@ credential-store: title: Welcome to Credential Stores titles: new: New Credential Store + worker-filter: + description: A filter used to control which PKI workers can handle Vault requests, allowing the use of private Vault instances with Boundary. + messages: + none: + description: You haven't added a worker filter yet. + edit: + description: Specify workers that have access to the credential store. actions: delete: Delete Credential Store types: @@ -865,9 +886,6 @@ credential-store: help: A PEM-encoded private key matching the client certificate from Client Certificate. client_certificate_key_hmac: label: Client Certificate Key HMAC - worker_filter: - label: Worker Filter - help: A filter used to control which PKI workers can handle Vault requests, allowing the use of private Vault instances with Boundary. credential-library: title: Credential Library title_plural: Credential Libraries @@ -1001,7 +1019,6 @@ managed-group: help: The list of groups that this Managed Group belongs to. worker: title: Worker - title_plural: Workers titles: new: New PKI Worker description: Workers are the server components that perform the session handling. diff --git a/addons/rose/.template-lintrc.js b/addons/rose/.template-lintrc.js index 37b21eccaf..0cfbd1f3b3 100644 --- a/addons/rose/.template-lintrc.js +++ b/addons/rose/.template-lintrc.js @@ -10,11 +10,7 @@ module.exports = { extends: ['recommended', 'ember-template-lint-plugin-prettier:recommended'], overrides: [ { - files: [ - 'addon/components/rose/anonymous/index.hbs', - 'addon/components/rose/table/row/cell/index.hbs', - 'addon/components/rose/anonymous/index.hbs', - ], + files: ['addon/components/rose/anonymous/index.hbs'], rules: { 'no-yield-only': false, }, diff --git a/addons/rose/addon/components/rose/link-button/index.hbs b/addons/rose/addon/components/rose/link-button/index.hbs deleted file mode 100644 index bdf8b42342..0000000000 --- a/addons/rose/addon/components/rose/link-button/index.hbs +++ /dev/null @@ -1,32 +0,0 @@ -{{! - Copyright (c) HashiCorp, Inc. - SPDX-License-Identifier: BUSL-1.1 -}} - - - - {{#if @iconLeft}} - - {{/if}} - - - {{yield}} - - - {{#if @iconOnly}} - - {{/if}} - - {{#if @iconRight}} - - {{/if}} - - \ No newline at end of file diff --git a/addons/rose/addon/components/rose/message/index.hbs b/addons/rose/addon/components/rose/message/index.hbs deleted file mode 100644 index 526e5c7cc0..0000000000 --- a/addons/rose/addon/components/rose/message/index.hbs +++ /dev/null @@ -1,33 +0,0 @@ -{{! - Copyright (c) HashiCorp, Inc. - SPDX-License-Identifier: BUSL-1.1 -}} - -
- - {{#if @title}} -
- {{#if @icon}} - - {{/if}} - -

{{@title}}

- - {{#if @subtitle}} -

{{@subtitle}}

- {{/if}} -
- {{/if}} - -
- {{yield - (hash - description=(component - 'rose/anonymous' tagName='p' class='rose-message-description' - ) - link=(component 'link-to') - ) - }} -
- -
\ No newline at end of file diff --git a/addons/rose/addon/components/rose/message/index.js b/addons/rose/addon/components/rose/message/index.js deleted file mode 100644 index d10bcebbb5..0000000000 --- a/addons/rose/addon/components/rose/message/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -import Component from '@glimmer/component'; - -export default class RoseMessageComponent extends Component {} diff --git a/addons/rose/addon/components/rose/separator/index.hbs b/addons/rose/addon/components/rose/separator/index.hbs deleted file mode 100644 index 1b08eec797..0000000000 --- a/addons/rose/addon/components/rose/separator/index.hbs +++ /dev/null @@ -1,6 +0,0 @@ -{{! - Copyright (c) HashiCorp, Inc. - SPDX-License-Identifier: BUSL-1.1 -}} - -
\ No newline at end of file diff --git a/addons/rose/addon/components/rose/table/index.hbs b/addons/rose/addon/components/rose/table/index.hbs deleted file mode 100644 index 7b3729acd5..0000000000 --- a/addons/rose/addon/components/rose/table/index.hbs +++ /dev/null @@ -1,27 +0,0 @@ -{{! - Copyright (c) HashiCorp, Inc. - SPDX-License-Identifier: BUSL-1.1 -}} - - - {{#if @caption}} - - {{/if}} - - {{yield - (hash - header=(component - 'rose/table/section' tagName='thead' classNames='rose-table-header' - ) - body=(component - 'rose/table/section' tagName='tbody' classNames='rose-table-body' - ) - footer=(component - 'rose/table/section' tagName='tfoot' classNames='rose-table-footer' - ) - ) - }} -
{{@caption}}
\ No newline at end of file diff --git a/addons/rose/addon/components/rose/table/row/cell/index.hbs b/addons/rose/addon/components/rose/table/row/cell/index.hbs deleted file mode 100644 index 8709fc4b8b..0000000000 --- a/addons/rose/addon/components/rose/table/row/cell/index.hbs +++ /dev/null @@ -1,6 +0,0 @@ -{{! - Copyright (c) HashiCorp, Inc. - SPDX-License-Identifier: BUSL-1.1 -}} - -{{yield}} \ No newline at end of file diff --git a/addons/rose/addon/components/rose/table/row/cell/index.js b/addons/rose/addon/components/rose/table/row/cell/index.js deleted file mode 100644 index c9d2f35f32..0000000000 --- a/addons/rose/addon/components/rose/table/row/cell/index.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -import Component from '@ember/component'; -import layout from './index'; -import { computed } from '@ember/object'; - -export default Component.extend({ - layout, - tagName: 'td', - classNameBindings: ['cellTypeStyle', 'cellShrink', 'cellAlignmentStyle'], - cellTypeStyle: computed(function () { - return this.tagName.match('th') - ? `rose-table-header-cell` - : 'rose-table-cell'; - }), - cellShrink: computed(function () { - if (this.shrink) { - return 'rose-table-cell-shrink'; - } - }), - cellAlignmentStyle: computed(function () { - if (this.alignRight) { - return 'align-right'; - } - - if (this.alignCenter) { - return 'align-center'; - } - }), -}); diff --git a/addons/rose/addon/components/rose/table/row/index.hbs b/addons/rose/addon/components/rose/table/row/index.hbs deleted file mode 100644 index d5427c002c..0000000000 --- a/addons/rose/addon/components/rose/table/row/index.hbs +++ /dev/null @@ -1,18 +0,0 @@ -{{! - Copyright (c) HashiCorp, Inc. - SPDX-License-Identifier: BUSL-1.1 -}} - - - {{yield - (hash - cell=(component 'rose/table/row/cell') - headerCell=(component 'rose/table/row/cell' tagName='th') - ) - }} - \ No newline at end of file diff --git a/addons/rose/addon/components/rose/table/row/index.js b/addons/rose/addon/components/rose/table/row/index.js deleted file mode 100644 index e3b8e5934d..0000000000 --- a/addons/rose/addon/components/rose/table/row/index.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -import Component from '@ember/component'; -import layout from './index'; - -/** - * A table row component that uses tr table element and - * configures a table cell element. - */ -export default Component.extend({ - layout, - tagName: '', -}); diff --git a/addons/rose/addon/components/rose/table/section/index.hbs b/addons/rose/addon/components/rose/table/section/index.hbs deleted file mode 100644 index f5b5318c1c..0000000000 --- a/addons/rose/addon/components/rose/table/section/index.hbs +++ /dev/null @@ -1,6 +0,0 @@ -{{! - Copyright (c) HashiCorp, Inc. - SPDX-License-Identifier: BUSL-1.1 -}} - -{{yield (hash row=(component 'rose/table/row'))}} \ No newline at end of file diff --git a/addons/rose/addon/components/rose/table/section/index.js b/addons/rose/addon/components/rose/table/section/index.js deleted file mode 100644 index 5deef75b06..0000000000 --- a/addons/rose/addon/components/rose/table/section/index.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -import Component from '@ember/component'; -import layout from './index'; - -/** - * A table section component that configures a row component. - * Section can be: thead, tbody, or tfoot table elements. - */ -export default Component.extend({ - layout, - tagName: '', -}); diff --git a/addons/rose/addon/utilities/component-auto-id.js b/addons/rose/addon/utilities/component-auto-id.js index cdfc1f3a99..1d52d9c89a 100644 --- a/addons/rose/addon/utilities/component-auto-id.js +++ b/addons/rose/addon/utilities/component-auto-id.js @@ -3,12 +3,12 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { v1 } from 'ember-uuid'; +import { v4 as uuidv4 } from 'uuid'; /** * A function that generates a component ID, based on UUID. * @return {string} */ export function generateComponentID() { - return `component-${v1()}`; + return `component-${uuidv4()}`; } diff --git a/addons/rose/app/components/rose/link-button.js b/addons/rose/app/components/rose/link-button.js deleted file mode 100644 index 3299c8573e..0000000000 --- a/addons/rose/app/components/rose/link-button.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -export { default } from 'rose/components/rose/link-button'; diff --git a/addons/rose/app/components/rose/message.js b/addons/rose/app/components/rose/message.js deleted file mode 100644 index cc99e8c15a..0000000000 --- a/addons/rose/app/components/rose/message.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -export { default } from 'rose/components/rose/message'; diff --git a/addons/rose/app/components/rose/notification.js b/addons/rose/app/components/rose/notification.js deleted file mode 100644 index 61de3f2453..0000000000 --- a/addons/rose/app/components/rose/notification.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -export { default } from 'rose/components/rose/notification'; diff --git a/addons/rose/app/components/rose/separator.js b/addons/rose/app/components/rose/separator.js deleted file mode 100644 index 6e9b10eca4..0000000000 --- a/addons/rose/app/components/rose/separator.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -export { default } from 'rose/components/rose/separator'; diff --git a/addons/rose/app/components/rose/table.js b/addons/rose/app/components/rose/table.js deleted file mode 100644 index c9a89893bb..0000000000 --- a/addons/rose/app/components/rose/table.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -export { default } from 'rose/components/rose/table'; diff --git a/addons/rose/app/components/rose/table/row.js b/addons/rose/app/components/rose/table/row.js deleted file mode 100644 index 85687342c9..0000000000 --- a/addons/rose/app/components/rose/table/row.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -export { default } from 'rose/components/rose/table/row'; diff --git a/addons/rose/app/components/rose/table/row/cell.js b/addons/rose/app/components/rose/table/row/cell.js deleted file mode 100644 index d437a26a15..0000000000 --- a/addons/rose/app/components/rose/table/row/cell.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -export { default } from 'rose/components/rose/table/row/cell'; diff --git a/addons/rose/app/components/rose/table/section.js b/addons/rose/app/components/rose/table/section.js deleted file mode 100644 index ad07f158ff..0000000000 --- a/addons/rose/app/components/rose/table/section.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -export { default } from 'rose/components/rose/table/section'; diff --git a/addons/rose/app/styles/rose/components/_frame.scss b/addons/rose/app/styles/rose/components/_frame.scss index b4bf945de2..c4afa29e6b 100644 --- a/addons/rose/app/styles/rose/components/_frame.scss +++ b/addons/rose/app/styles/rose/components/_frame.scss @@ -42,10 +42,5 @@ $xxxxs: sizing.rems(xxxxs); // 1 $body-padding: $l; padding: $body-padding; - - > .rose-separator { - margin-left: -$body-padding; - margin-right: -$body-padding; - } } } diff --git a/addons/rose/app/styles/rose/components/_index.scss b/addons/rose/app/styles/rose/components/_index.scss index e79b7def84..c827364151 100644 --- a/addons/rose/app/styles/rose/components/_index.scss +++ b/addons/rose/app/styles/rose/components/_index.scss @@ -3,23 +3,21 @@ * SPDX-License-Identifier: BUSL-1.1 */ +// This list is sorted in ascending order to improve readability + @import 'button'; +@import 'card'; +@import 'cards'; +@import 'code-editor'; @import 'dialog'; @import 'dropdown'; @import 'form'; @import 'frame'; @import 'header'; -@import 'layout'; @import 'icon'; +@import 'layout'; @import 'list'; -@import 'card'; -@import 'cards'; -@import 'page-header'; -@import 'separator'; +@import 'metadata-list'; @import 'nav'; -@import 'table'; -@import 'message'; -@import 'link-button'; +@import 'page-header'; @import 'toolbar'; -@import 'code-editor'; -@import 'metadata-list'; diff --git a/addons/rose/app/styles/rose/components/_link-button.scss b/addons/rose/app/styles/rose/components/_link-button.scss deleted file mode 100644 index bec0911d1a..0000000000 --- a/addons/rose/app/styles/rose/components/_link-button.scss +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -// Link button uses rose-button styling -.rose-link-button { - text-decoration: none; -} diff --git a/addons/rose/app/styles/rose/components/_message.scss b/addons/rose/app/styles/rose/components/_message.scss deleted file mode 100644 index 7de93d0ce9..0000000000 --- a/addons/rose/app/styles/rose/components/_message.scss +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -@use '../utilities/type'; -@use '../variables/sizing'; -@use '../variables/media'; - -.rose-message { - --message-color: var(--ui-gray-starker-1); - - display: flex; - flex-direction: column; - color: var(--message-color); - width: sizing.rems(l) * 15; // 16 rems/320 px - margin-bottom: sizing.rems(l); - - @media (width <= (media.width(small))) { - width: auto; - } - - // Icons always need tweaking - .rose-icon { - --size: #{sizing.rems(m)}; - - > svg { - vertical-align: text-bottom; - } - } - - .rose-message-header { - display: grid; - grid-template-areas: - 'icon title' - 'icon subtitle'; - grid-auto-columns: auto 1fr; - margin-bottom: sizing.rems(m); - - .rose-icon { - // targeting 40px - --size: #{sizing.rems(xl) + sizing.rems(xxs)}; - - grid-area: icon; - margin-right: sizing.rems(s); - } - - .rose-message-title { - @include type.type(l, semibold); - - grid-area: title; - margin: 0; - } - - .rose-message-subtitle { - @include type.type(s, normal); - - grid-area: subtitle; - margin: 0; - } - } - - .rose-message-body { - display: grid; - grid-template-areas: - 'description description' - 'left-link right-link'; - - .rose-message-description { - grid-area: description; - margin-bottom: sizing.rems(l); - } - } -} diff --git a/addons/rose/app/styles/rose/components/_separator.scss b/addons/rose/app/styles/rose/components/_separator.scss deleted file mode 100644 index fb7223af44..0000000000 --- a/addons/rose/app/styles/rose/components/_separator.scss +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -@use '../variables/sizing'; - -.rose-separator { - background-color: var(--ui-gray-subtler-3); - border: 0; - height: sizing.rems(xxxxs); - margin: sizing.rems(l) 0; -} diff --git a/addons/rose/app/styles/rose/components/table/_index.scss b/addons/rose/app/styles/rose/components/table/_index.scss deleted file mode 100644 index 595c64a7c6..0000000000 --- a/addons/rose/app/styles/rose/components/table/_index.scss +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -@import 'table'; diff --git a/addons/rose/app/styles/rose/components/table/_table.scss b/addons/rose/app/styles/rose/components/table/_table.scss deleted file mode 100644 index 64d5eee526..0000000000 --- a/addons/rose/app/styles/rose/components/table/_table.scss +++ /dev/null @@ -1,120 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -@use '../../variables/sizing'; -@use '../../utilities/type'; -@use 'sass:math'; - -.rose-table { - @include type.type(s, normal); - - width: 100%; - - .rose-table-row { - border-bottom: sizing.rems(xxxxs) solid var(--ui-gray-subtler-3); - text-align: left; - - &:last-child { - border-bottom: 0; - } - } - - // This styles are add to stop the input to apply extra margin (default on forms) - .rose-form-input, - .rose-form-input-field, - .hds-form-select, - [class^='hds-form-field'], - [class^='hds-form-field']:not(:first-child) { - margin: 0; - width: 100%; - } - - // Fix margin issue with textarea elements inside tables - textarea { - display: block; - } - - .rose-table-cell, - .rose-table-header-cell { - vertical-align: text-top; - - &.align-right { - text-align: right; - } - - &.align-center { - text-align: center; - } - } - - .rose-table-header-cell { - @include type.type(s, semibold); - } - - .rose-table-header .rose-table-header-cell { - color: var(--ui-gray); - } - - .rose-table-header, - .rose-table-footer { - .rose-table-cell, - .rose-table-header-cell { - padding: sizing.rems(s); - } - } - - .rose-table-body { - .rose-table-cell, - .rose-table-header-cell { - padding: sizing.rems(m) sizing.rems(s); - } - } - - .rose-table-cell-shrink { - width: 1%; - white-space: nowrap; - } - - [class*='rose-button'] { - margin-bottom: 0; - } - - &.rose-table-condensed { - .rose-table-cell, - .rose-table-header-cell { - @include type.type(s); - - $line-height: math.div(36, 14); - - line-height: $line-height; - padding: sizing.rems(xxs) + sizing.rems(xxxxs) sizing.rems(s); - - &:first-child { - padding-left: 0; - } - } - - .rose-table-header { - border-bottom: sizing.rems(xxxxs) solid var(--ui-gray-subtler-3); - - .rose-table-header-cell { - $line-height: math.div(16, 14); - - line-height: $line-height; - padding: sizing.rems(xs) sizing.rems(s); - - &:first-child { - padding-left: 0; - } - } - } - } - - .rose-table-row-visually-hidden { - position: absolute; - top: -9999rem; - left: -9999rem; - } -} diff --git a/addons/rose/app/styles/rose/variables/color/_css.scss b/addons/rose/app/styles/rose/variables/color/_css.scss index 627170117b..6e2b72e340 100644 --- a/addons/rose/app/styles/rose/variables/color/_css.scss +++ b/addons/rose/app/styles/rose/variables/color/_css.scss @@ -76,6 +76,8 @@ --ui-gray-starker-3: #{color(ui-gray, 800)}; --ui-gray-starker-4: #{color(ui-gray, 900)}; --ui-header-subtler: #{color(ui-cool-gray, 700)}; + + color-scheme: light; } // Dark Mode diff --git a/addons/rose/package.json b/addons/rose/package.json index 76fc8d10c3..70bf37b99f 100644 --- a/addons/rose/package.json +++ b/addons/rose/package.json @@ -40,9 +40,9 @@ "ember-composable-helpers": "^5.0.0", "ember-focus-trap": "^1.0.1", "ember-named-blocks-polyfill": "^0.2.5", - "ember-uuid": "^2.1.0", "jsonlint": "^1.6.3", - "sass": "^1.69.5" + "sass": "^1.69.5", + "uuid": "^11.0.3" }, "devDependencies": { "@babel/core": "^7.25.2", @@ -97,9 +97,6 @@ "stylelint-config-standard-scss": "^11.0.0", "webpack": "^5.94.0" }, - "peerDependencies": { - "ember-source": "^4.0.0" - }, "engines": { "node": "20.* || 22.*" }, diff --git a/addons/rose/tests/integration/components/rose/link-button-test.js b/addons/rose/tests/integration/components/rose/link-button-test.js deleted file mode 100644 index 98513986b9..0000000000 --- a/addons/rose/tests/integration/components/rose/link-button-test.js +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -import { module, test } from 'qunit'; -import { setupRenderingTest } from 'ember-qunit'; -import { render, find } from '@ember/test-helpers'; -import { hbs } from 'ember-cli-htmlbars'; - -module('Integration | Component | rose/link-button', function (hooks) { - setupRenderingTest(hooks); - - test('it renders', async function (assert) { - await render(hbs``); - assert.ok(find('a')); - assert.notOk(find('.rose-button')); - assert.ok(find('.rose-link-button')); - }); - - test('it renders with attributes', async function (assert) { - await render(hbs``); - assert.ok(find('#link-button')); - }); - - test('it renders with content', async function (assert) { - await render( - hbs`Link button content`, - ); - assert.ok( - find('.rose-link-button').textContent.trim(), - 'Link button content', - ); - }); - - test('it renders with rose button styles', async function (assert) { - await render(hbs``); - assert.ok(find(`.rose-button-sample`)); - }); - - test('it supports left icon with @iconLeft', async function (assert) { - await render( - hbs``, - ); - assert.ok(find('.rose-icon')); - assert.ok(find('.has-icon-left')); - }); - - test('it supports left icon with @iconRight', async function (assert) { - await render( - hbs``, - ); - assert.ok(find('.rose-icon')); - assert.ok(find('.has-icon-right')); - }); - - test('it supports only icon with @iconOnly', async function (assert) { - await render( - hbs``, - ); - assert.ok(find('.rose-icon')); - assert.ok(find('.has-icon-only')); - }); -}); diff --git a/addons/rose/tests/integration/components/rose/message-test.js b/addons/rose/tests/integration/components/rose/message-test.js deleted file mode 100644 index 48b5675bc8..0000000000 --- a/addons/rose/tests/integration/components/rose/message-test.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -import { module, test } from 'qunit'; -import { setupRenderingTest } from 'ember-qunit'; -import { render, find } from '@ember/test-helpers'; -import { hbs } from 'ember-cli-htmlbars'; - -module('Integration | Component | rose/message', function (hooks) { - setupRenderingTest(hooks); - - test('it renders', async function (assert) { - await render(hbs` - - Description - Link - - `); - - assert.ok(find('.rose-message')); - assert.strictEqual(find('.rose-message-title').textContent.trim(), 'Title'); - assert.strictEqual( - find('.rose-message-subtitle').textContent.trim(), - 'Subtitle', - ); - assert.strictEqual( - find('.rose-message-description').textContent.trim(), - 'Description', - ); - - assert.strictEqual(find('a').textContent.trim(), 'Link'); - }); -}); diff --git a/addons/rose/tests/integration/components/rose/separator-test.js b/addons/rose/tests/integration/components/rose/separator-test.js deleted file mode 100644 index 72582ce797..0000000000 --- a/addons/rose/tests/integration/components/rose/separator-test.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -import { module, test } from 'qunit'; -import { setupRenderingTest } from 'ember-qunit'; -import { render, find } from '@ember/test-helpers'; -import { hbs } from 'ember-cli-htmlbars'; - -module('Integration | Component | rose/separator', function (hooks) { - setupRenderingTest(hooks); - - test('it renders', async function (assert) { - await render(hbs``); - assert.ok(find('.rose-separator')); - }); -}); diff --git a/addons/rose/tests/integration/components/rose/table-test.js b/addons/rose/tests/integration/components/rose/table-test.js deleted file mode 100644 index e3dae9612c..0000000000 --- a/addons/rose/tests/integration/components/rose/table-test.js +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -import { module, test } from 'qunit'; -import { setupRenderingTest } from 'ember-qunit'; -import { render, find } from '@ember/test-helpers'; -import { hbs } from 'ember-cli-htmlbars'; - -module('Integration | Component | rose/table', function (hooks) { - setupRenderingTest(hooks); - - test('it renders', async function (assert) { - await render(hbs``); - assert.ok(find('table')); - assert.ok(find('.rose-table')); - }); - - test('it renders with attributes', async function (assert) { - await render(hbs``); - assert.ok(find('#table')); - }); - - test('it renders with caption', async function (assert) { - await render(hbs``); - assert.ok(find('caption')); - assert.strictEqual(find('caption').textContent.trim(), 'table caption'); - }); - - test('it renders with header', async function (assert) { - await render(hbs` - - `); - assert.ok(find('thead')); - }); - - test('it renders with body', async function (assert) { - await render(hbs` - - `); - assert.ok(find('tbody')); - }); - - test('it renders with footer', async function (assert) { - await render(hbs` - - `); - assert.ok(find('tfoot')); - }); - - test('it supports a style class', async function (assert) { - await render(hbs``); - assert.ok(find('.rose-table-condensed')); - }); -}); diff --git a/addons/rose/tests/integration/components/rose/table/row-test.js b/addons/rose/tests/integration/components/rose/table/row-test.js deleted file mode 100644 index bc06375fd9..0000000000 --- a/addons/rose/tests/integration/components/rose/table/row-test.js +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -import { module, test } from 'qunit'; -import { setupRenderingTest } from 'ember-qunit'; -import { render, find } from '@ember/test-helpers'; -import { hbs } from 'ember-cli-htmlbars'; - -module('Integration | Component | rose/table/row', function (hooks) { - setupRenderingTest(hooks); - - test('it renders', async function (assert) { - assert.expect(2); - await render(hbs``); - assert.ok(find('tr')); - assert.ok(find('.rose-table-row')); - }); - - test('it renders with attributes', async function (assert) { - assert.expect(1); - await render(hbs``); - assert.ok(find('#row')); - }); - - test('it renders a cell', async function (assert) { - assert.expect(2); - await render(hbs` - - `); - assert.ok(find('td')); - assert.ok(find('.rose-table-cell')); - }); - - test('it renders a header cell', async function (assert) { - assert.expect(2); - await render(hbs` - - `); - assert.ok(find('th')); - assert.ok(find('.rose-table-header-cell')); - }); - - test('it can be hidden', async function (assert) { - assert.expect(1); - await render(hbs``); - assert.ok(find('.rose-table-row-visually-hidden')); - }); -}); diff --git a/addons/rose/tests/integration/components/rose/table/row/cell-test.js b/addons/rose/tests/integration/components/rose/table/row/cell-test.js deleted file mode 100644 index 74ce45cbd9..0000000000 --- a/addons/rose/tests/integration/components/rose/table/row/cell-test.js +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -import { module, test } from 'qunit'; -import { setupRenderingTest } from 'ember-qunit'; -import { render, find } from '@ember/test-helpers'; -import { hbs } from 'ember-cli-htmlbars'; - -module('Integration | Component | rose/table/row/cell', function (hooks) { - setupRenderingTest(hooks); - - test('it renders', async function (assert) { - assert.expect(2); - await render(hbs` - content - `); - assert.ok(find('td')); - assert.strictEqual(find('.rose-table-cell').textContent.trim(), 'content'); - }); - - test('it renders with attributes', async function (assert) { - assert.expect(1); - await render(hbs``); - assert.ok(find('#cell')); - }); - - test('it renders as header cell', async function (assert) { - assert.expect(4); - await render(hbs``); - assert.ok(find('th')); - assert.ok(find('.rose-table-header-cell')); - assert.notOk(find('td')); - assert.notOk(find('.rose-table-cell')); - }); - - test('it adds shrink style class', async function (assert) { - assert.expect(1); - await render(hbs``); - assert.ok(find('.rose-table-cell-shrink')); - }); - - test('it does not add expand style class in default cells', async function (assert) { - assert.expect(1); - await render(hbs``); - assert.notOk(find('.rose-table-cell-shrink')); - }); - - test('it renders with align center in a header cell', async function (assert) { - assert.expect(2); - await render( - hbs``, - ); - assert.ok(find('.rose-table-header-cell')); - assert.ok(find('.align-center')); - }); - - test('it renders with align center in a body', async function (assert) { - assert.expect(2); - await render(hbs``); - assert.ok(find('.rose-table-cell')); - assert.ok(find('.align-center')); - }); - - test('it renders with align right in a header cell', async function (assert) { - assert.expect(2); - await render( - hbs``, - ); - assert.ok(find('.rose-table-header-cell')); - assert.ok(find('.align-right')); - }); - - test('it renders with align right', async function (assert) { - assert.expect(2); - await render(hbs``); - assert.ok(find('.rose-table-cell')); - assert.ok(find('.align-right')); - }); -}); diff --git a/addons/rose/tests/integration/components/rose/table/section-test.js b/addons/rose/tests/integration/components/rose/table/section-test.js deleted file mode 100644 index ccad4cfd69..0000000000 --- a/addons/rose/tests/integration/components/rose/table/section-test.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -import { module, test } from 'qunit'; -import { setupRenderingTest } from 'ember-qunit'; -import { render, find } from '@ember/test-helpers'; -import { hbs } from 'ember-cli-htmlbars'; - -module('Integration | Component | rose/table/section', function (hooks) { - setupRenderingTest(hooks); - - test('it renders', async function (assert) { - await render(hbs``); - assert.ok(find('tag')); - assert.ok(find('.tag-class')); - }); - - test('it renders with attributes', async function (assert) { - await render(hbs``); - assert.ok(find('#section')); - }); - - test('it renders a row', async function (assert) { - await render(hbs` - - `); - assert.ok(find('tr')); - assert.ok(find('.rose-table-row')); - }); -}); diff --git a/docker-compose-embedding.yml b/docker-compose-embedding.yml deleted file mode 100755 index 2bc6ee1e20..0000000000 --- a/docker-compose-embedding.yml +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: BUSL-1.1 - -# Note: this will change the permissions of the files in this directory after -# running to allow for easy deletion, so this should only be run as part of -# boundary's build task. - -version: '3.8' - -services: - node: &node - image: docker.mirror.hashicorp.services/node:14 - volumes: - - './:/src' - - 'icu_node_modules:/src/node_modules' - - 'icu_addons_api_node_modules:/src/addons/api/node_modules' - - 'icu_addons_auth_node_modules:/src/addons/auth/node_modules' - - 'icu_addons_core_node_modules:/src/addons/core/node_modules' - - 'icu_addons_rose_node_modules:/src/addons/rose/node_modules' - - 'icu_ui_admin_node_modules:/src/ui/admin/node_modules' - - 'icu_ui_desktop_node_modules:/src/ui/desktop/node_modules' - working_dir: /src - - build-admin: - <<: *node - entrypoint: bash - command: '-c "yarn install && yarn build:ui:admin && chmod -R 777 ."' - - build-desktop-production: - <<: *node - entrypoint: bash - command: '-c "yarn install --frozen-lockfile --non-interactive && yarn build:ui:desktop:production && chmod -R 777 ."' - -volumes: - icu_node_modules: - icu_addons_api_node_modules: - icu_addons_auth_node_modules: - icu_addons_core_node_modules: - icu_addons_rose_node_modules: - icu_ui_admin_node_modules: - icu_ui_desktop_node_modules: diff --git a/e2e-tests/.gitignore b/e2e-tests/.gitignore index 72f0a85150..fc8ad50f76 100644 --- a/e2e-tests/.gitignore +++ b/e2e-tests/.gitignore @@ -2,3 +2,5 @@ admin/artifacts desktop/artifacts playwright-report + +.auth \ No newline at end of file diff --git a/e2e-tests/README.md b/e2e-tests/README.md new file mode 100644 index 0000000000..b7b867c087 --- /dev/null +++ b/e2e-tests/README.md @@ -0,0 +1,222 @@ + + + +- [Boundary UI E2E tests](#boundary-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 Enos:](#setup-enos) + - [Run tests:](#run-tests) + - [Launch Enos Scenario](#launch-enos-scenario) + - [Admin](#admin) + - [Desktop](#desktop) + - [Destroy Enos Scenario](#destroy-enos-scenario) + - [Developing Tests](#developing-tests) + + + +# Boundary UI E2E tests + +This test suite tests the Boundary admin UI and desktop client 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. 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/) installed. To securely store your SSH keys and tokens we recommend using 1Password. + +### Accesses + +- Doormat account: `boundary_team_acctest_dev`. + - Request access through Doormat. + +### 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 + +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. + +```bash +which boundary +``` + +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 +cd boundary-ui/e2e-tests/admin +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. We recommend `us-east-1`. [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/AWSEC2/latest/UserGuide/create-key-pairs.html). + +If you would like to do it through the command line: + +```bash +doormat login +source <(doormat aws export --account boundary_team_acctest_dev) +# Feel free to use a custom name for your key pair if you don't want to use $USER, just make sure it follows the _enos format so it can be identified. e.g. zed_enos +ssh-keygen -N '' -t ed25519 -f ~/.ssh/"$USER"_enos +aws ec2 import-key-pair \ + --region us-east-1 \ + --key-name "$USER"_enos \ + --public-key-material fileb://~/.ssh/"$USER"_enos.pub +``` + +### Setup Enos: + +Make sure you're in the correct boundary repository for the version of Boundary you plan on using. +The enos scenarios for CE need to be run in `boundary` and the enos scenarios for enterprise need to be run in `boundary-enterprise`. + +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: + +- `boundary_edition`: The edition of Boundary you are using. Options are `oss` or `enterprise`. +- `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 tagged resources in AWS. +- `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). + +> [!TIP] +> To run e2e tests against a local branch for Admin UI, update the UI commit in boundary so it gets picked up when enos builds the binary + +By default, enos will build the version of the UI that is specified in the `VERSION` commit for `boundary`. To use a specific branch, update the version first before running the scenario. + +In the `boundary` or `boundary-enterprise` repository: +```bash +UI_COMMITISH= make update-ui-version + +# Alternatively if you want to just be able to run the same command for the most recent commit +UI_COMMITISH=$(git -C rev-parse HEAD) make update-ui-version +``` + +## Run tests: + +Before running the e2e test locally, we need to launch an 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 used to run enos (Boundary). +- Terminal 2: Will be used to run e2e UI tests (Boundary UI). + +### Launch Enos Scenario + +Using Terminal 1: + +- `cd boundary/enos` or `cd boundary_enterprise/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_aws builder:local` or `enos scenario launch e2e_ui_aws_ent builder:local` if in enterprise. + - Launches enos scenario, this will take from 5 to 10 minutes. When its done, you will see a Enos Operations finished! within your terminal. Check out more scenarios [here](https://github.com/hashicorp/boundary/tree/main/enos). +- `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. + +Alternatively, you can also redirect the output to an `.env` file that will get picked up by tests automatically which is useful if you're running tests in your IDE: +``` +bash scripts/test_e2e_env.sh > (boundary-ui directory)/e2e-tests/.env +``` + +> [!IMPORTANT] +> Be aware that once the scenario is launched you will create and run resources within AWS. After you are done using the scenario, [you should destroy it](#destroy-enos-scenario). + +### Admin + +Using Terminal 2: + +Set the env variables `test_e2e_env.sh` script output in this terminal. + +```bash +cd boundary-ui/e2e-tests +yarn run admin +``` + +Here are some additional commands to assist with debugging. + +```bash +PWDEBUG=console yarn playwright test --headed --config admin/playwright.config.js login.spec.js +PWDEBUG=console yarn playwright test --headed --config admin/playwright.config.js login.spec.js:13 --debug +PWDEBUG=console yarn playwright test --headed --config admin/playwright.config.js login.spec.js --debug +``` + +To run all tests for a certain configuration, you can use the following shortcuts + +```bash +# Runs all tests pertaining to the Community Edition +yarn run e2e:ce:aws +yarn run e2e:ce:docker + +# Runs all tests pertaining to the Enterprise Edition +yarn run e2e:ent:aws +yarn run e2e:ent:docker +``` + +### Desktop + +> [!NOTE] +> Currently the desktop client e2e tests assumes that the Boundary server is an enterprise edition. + +Using Terminal 2: + +Set the env variables `test_e2e_env.sh` script output in this terminal. + +```bash +cd boundary-ui/e2e-tests +yarn run desktop +``` + +[Playwright documentation about running tests](https://playwright.dev/docs/running-tests). + +### Destroy Enos Scenario + +Using Terminal 1: + +```bash +# Community Edition +enos scenario destroy e2e_ui_aws builder:local + +# Enterprise Edition +enos scenario destroy e2e_ui_aws_ent builder:local +``` + +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` +should be the most prioritized locator as this is the closest way to how users and +accessibility features perceive the page. `getByLabel` should be used for form fields. + +Additional information can be found [here](https://playwright.dev/docs/locators#locating-elements) +and [here](https://testing-library.com/docs/queries/about/#priority). diff --git a/e2e-tests/admin/README.md b/e2e-tests/admin/README.md deleted file mode 100644 index 8f6092ebdc..0000000000 --- a/e2e-tests/admin/README.md +++ /dev/null @@ -1,169 +0,0 @@ - - - -# Admin UI e2e tests - -- [Admin UI e2e tests](#admin-ui-e2e-tests) -- [Admin UI e2e tests](#admin-ui-e2e-tests-1) - - [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-1) - - [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. 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 - -- Doormat account: `boundary_team_acctest_dev`. - - Request access through Doormat. -- HCP account: you will need to use Terraform cloud to spin up enos. -- app.terraform.io org: `hashicorp-qti`. - - Request access in `#proj-boundary-qe` slack channel. - -### 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 - -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. -```bash -which boundary -``` - -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 -cd boundary-ui/ui/admin -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). Make sure you are added to the `hashicorp-qti` org in order for the created token to be valid. - -**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_src_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_aws 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. Check out more scenarios [here](https://github.com/hashicorp/boundary/tree/main/enos). -- `$ 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 -PWDEBUG=console yarn playwright test --headed --config ./tests/e2e/playwright.config.js login.spec.js -PWDEBUG=console yarn playwright test --headed --config ./tests/e2e/playwright.config.js login.spec.js:13 --debug -PWDEBUG=console yarn playwright test --headed --config ./tests/e2e/playwright.config.js login.spec.js --debug -``` - -To run all tests for a certain configuration, you can use the following shortcuts -```bash -# Runs all tests pertaining to the Community Edition -yarn run e2e:ce:aws -yarn run e2e:ce:docker - -# Runs all tests pertaining to the Enterprise Edition -yarn run e2e:ent:aws -yarn run e2e:ent:docker -``` - -[Playwright documentation about running tests](https://playwright.dev/docs/running-tests). - -### Destroy Enos Scenario - -Using Terminal 1: -- `$ enos scenario destroy e2e_ui_aws builder:local` -- 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` -should be the most prioritized locator as this is the closest way to how users and -accessibility features perceive the page. `getByLabel` should be used for form fields. - -Additional information can be found [here](https://playwright.dev/docs/locators#locating-elements) -and [here](https://testing-library.com/docs/queries/about/#priority). diff --git a/e2e-tests/admin/global-setup.js b/e2e-tests/admin/global-setup.js deleted file mode 100644 index 6988b5cc46..0000000000 --- a/e2e-tests/admin/global-setup.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -import { chromium } from '@playwright/test'; -import { checkEnv } from '../helpers/general.js'; - -import { LoginPage } from './pages/login.js'; - -async function globalSetup() { - await checkEnv([ - 'BOUNDARY_ADDR', - 'E2E_PASSWORD_ADMIN_LOGIN_NAME', - 'E2E_PASSWORD_ADMIN_PASSWORD', - 'E2E_PASSWORD_AUTH_METHOD_ID', - ]); - - // Log in and save the authenticated state to reuse in tests - const browser = await chromium.launch(); - const page = await browser.newPage(); - await page.goto(process.env.BOUNDARY_ADDR); - - const loginPage = new LoginPage(page); - await loginPage.login( - process.env.E2E_PASSWORD_ADMIN_LOGIN_NAME, - process.env.E2E_PASSWORD_ADMIN_PASSWORD, - ); - const storageState = await page - .context() - .storageState({ path: authenticatedState }); - - const state = JSON.parse(storageState.origins[0].localStorage[0].value); - - // Set the token in the environment for use in API requests - process.env.E2E_TOKEN = state.authenticated.token; - - await browser.close(); -} - -export default globalSetup; - -export const authenticatedState = './admin/artifacts/authenticated-state.json'; diff --git a/e2e-tests/admin/pages/auth-methods.js b/e2e-tests/admin/pages/auth-methods.js index 8c333ba4aa..b1d40c71df 100644 --- a/e2e-tests/admin/pages/auth-methods.js +++ b/e2e-tests/admin/pages/auth-methods.js @@ -33,6 +33,97 @@ export class AuthMethodsPage extends BaseResourcePage { return authMethodName; } + /** + * Creates a new OIDC Auth Method. Assumes you have selected the desired scope. + * @param {string} issuer OIDC issuer + * @param {string} clientId OIDC client ID + * @param {string} clientSecret OIDC client secret + * @returns Name of the auth method + */ + async createOidcAuthMethod(issuer, clientId, clientSecret, boundaryAddr) { + const authMethodName = 'Auth Method ' + nanoid(); + await this.page + .getByRole('navigation', { name: 'IAM' }) + .getByRole('link', { name: 'Auth Methods' }) + .click(); + await this.page.getByRole('button', { name: 'New' }).click(); + await this.page.getByRole('link', { name: 'OIDC' }).click(); + await this.page.getByLabel('Name').fill(authMethodName); + await this.page.getByLabel('Description').fill('OIDC Auth Method'); + await this.page.getByLabel('Issuer').fill(issuer); + await this.page.getByLabel('Client ID').fill(clientId); + await this.page.getByLabel('Client Secret').fill(clientSecret); + await this.page + .getByRole('group', { name: 'Signing Algorithms' }) + .getByRole('combobox') + .selectOption('RS256'); + await this.page + .getByRole('group', { name: 'Signing Algorithms' }) + .getByRole('button', { name: 'Add' }) + .click(); + + await this.page + .getByRole('group', { name: 'Claims Scopes' }) + .getByRole('textbox') + .last() + .fill('groups'); + await this.page + .getByRole('group', { name: 'Claims Scopes' }) + .getByRole('button', { name: 'Add' }) + .click(); + await this.page + .getByRole('group', { name: 'Claims Scopes' }) + .getByRole('textbox') + .last() + .fill('user'); + await this.page + .getByRole('group', { name: 'Claims Scopes' }) + .getByRole('button', { name: 'Add' }) + .click(); + + await this.page + .getByRole('group', { name: 'Account Claim Maps' }) + .getByLabel('From Claim') + .last() + .fill('username'); + await this.page + .getByRole('group', { name: 'Account Claim Maps' }) + .getByLabel('To Claim') + .last() + .selectOption('name'); + await this.page + .getByRole('group', { name: 'Account Claim Maps' }) + .getByRole('button', { name: 'Add' }) + .click(); + await this.page + .getByRole('group', { name: 'Account Claim Maps' }) + .getByLabel('From Claim') + .last() + .fill('email'); + await this.page + .getByRole('group', { name: 'Account Claim Maps' }) + .getByLabel('To Claim') + .last() + .selectOption('email'); + await this.page + .getByRole('group', { name: 'Account Claim Maps' }) + .getByRole('button', { name: 'Add' }) + .click(); + + await this.page.getByLabel('Maximum Age').fill('20'); + await this.page.getByLabel('API URL Prefix').fill(boundaryAddr); + + await this.page.getByRole('button', { name: 'Save' }).click(); + await this.dismissSuccessAlert(); + await expect( + this.page + .getByRole('navigation', { name: 'breadcrumbs' }) + .getByText(authMethodName), + ).toBeVisible(); + + return authMethodName; + } + /** * Makes the first available auth method primary. * Assumes you have created new auth method. @@ -70,7 +161,6 @@ export class AuthMethodsPage extends BaseResourcePage { await this.page.getByRole('link', { name: 'Accounts' }).click(); await this.page - .getByRole('article') .getByRole('link', { name: 'Create Account', exact: true }) .click(); await this.page.getByLabel('Name (Optional)').fill(accountName); diff --git a/e2e-tests/admin/pages/groups.js b/e2e-tests/admin/pages/groups.js index 7b588e1bd2..0d6fde5300 100644 --- a/e2e-tests/admin/pages/groups.js +++ b/e2e-tests/admin/pages/groups.js @@ -39,7 +39,6 @@ export class GroupsPage extends BaseResourcePage { async addMemberToGroup(userName) { await this.page.getByRole('link', { name: 'Members', exact: true }).click(); await this.page - .getByRole('article') .getByRole('link', { name: 'Add Members', exact: true }) .click(); await this.page.getByRole('checkbox', { name: userName }).click(); diff --git a/e2e-tests/admin/pages/host-catalogs.js b/e2e-tests/admin/pages/host-catalogs.js index ac464e9db4..f2a6af1f41 100644 --- a/e2e-tests/admin/pages/host-catalogs.js +++ b/e2e-tests/admin/pages/host-catalogs.js @@ -37,7 +37,7 @@ export class HostCatalogsPage extends BaseResourcePage { await this.page.getByLabel('Name').fill(hostCatalogName); await this.page.getByLabel('Description').fill('This is an automated test'); await this.page - .getByRole('group', { name: 'Type' }) + .getByRole('group', { name: 'Type', exact: 'true' }) .getByLabel('Static') .click(); await this.page.getByRole('button', { name: 'Save' }).click(); diff --git a/e2e-tests/admin/pages/roles.js b/e2e-tests/admin/pages/roles.js index 96d38240ab..f9937a2bd2 100644 --- a/e2e-tests/admin/pages/roles.js +++ b/e2e-tests/admin/pages/roles.js @@ -41,7 +41,6 @@ export class RolesPage extends BaseResourcePage { .getByRole('link', { name: 'Principals', exact: true }) .click(); await this.page - .getByRole('article') .getByRole('link', { name: 'Add Principals', exact: true }) .click(); await this.page.getByRole('checkbox', { name: principalName }).click(); diff --git a/e2e-tests/admin/pages/sessions.js b/e2e-tests/admin/pages/sessions.js index b49492b5cb..a4158536fb 100644 --- a/e2e-tests/admin/pages/sessions.js +++ b/e2e-tests/admin/pages/sessions.js @@ -17,15 +17,16 @@ export class SessionsPage extends BasePage { .getByRole('navigation', { name: 'Resources' }) .getByRole('link', { name: 'Sessions' }) .click(); - await expect( - this.page - .getByRole('navigation', { name: 'breadcrumbs' }) - .getByText('Sessions'), - ).toBeVisible(); let i = 0; let sessionIsVisible = false; let sessionIsActive = false; do { + await expect( + this.page + .getByRole('navigation', { name: 'breadcrumbs' }) + .getByText('Sessions'), + ).toBeVisible(); + i = i + 1; sessionIsVisible = await this.page .getByRole('cell', { name: targetName }) diff --git a/e2e-tests/admin/pages/targets.js b/e2e-tests/admin/pages/targets.js index 5d6481bbe8..c007a13186 100644 --- a/e2e-tests/admin/pages/targets.js +++ b/e2e-tests/admin/pages/targets.js @@ -251,18 +251,14 @@ export class TargetsPage extends BaseResourcePage { ).toBeVisible(); const emptyLinkIsVisible = await this.page - .getByRole('article') .getByRole('link', { name: 'Add Host Sources', exact: true }) .isVisible(); - if (emptyLinkIsVisible) { - await this.page - .getByRole('article') - .getByRole('link', { name: 'Add Host Sources', exact: true }) - .click(); - } else { + if (!emptyLinkIsVisible) { await this.page.getByText('Manage').click(); - await this.page.getByRole('link', { name: 'Add Host Sources' }).click(); } + await this.page + .getByRole('link', { name: 'Add Host Sources', exact: true }) + .click(); await this.page .getByRole('cell', { name: hostSourceName }) @@ -370,21 +366,15 @@ export class TargetsPage extends BaseResourcePage { ).toBeVisible(); const addBrokeredCredentialsButtonIsVisible = await this.page - .getByRole('article') .getByRole('link', { name: 'Add Brokered Credentials', exact: true }) .isVisible(); - if (addBrokeredCredentialsButtonIsVisible) { - await this.page - .getByRole('article') - .getByRole('link', { name: 'Add Brokered Credentials', exact: true }) - .click(); - } else { + if (!addBrokeredCredentialsButtonIsVisible) { await this.page.getByText('Manage').click(); - await this.page - .getByRole('link', { name: 'Add Brokered Credentials' }) - .click(); } + await this.page + .getByRole('link', { name: 'Add Brokered Credentials', exact: true }) + .click(); await this.page .getByRole('cell', { name: credentialName }) @@ -427,27 +417,21 @@ export class TargetsPage extends BaseResourcePage { ).toBeVisible(); const addInjectedCredentialsButtonIsVisible = await this.page - .getByRole('article') .getByRole('link', { name: 'Add Injected Application Credentials', exact: true, }) .isVisible(); - if (addInjectedCredentialsButtonIsVisible) { - await this.page - .getByRole('article') - .getByRole('link', { - name: 'Add Injected Application Credentials', - exact: true, - }) - .click(); - } else { + if (!addInjectedCredentialsButtonIsVisible) { await this.page.getByText('Manage').click(); - await this.page - .getByRole('link', { name: 'Add Injected Application' }) - .click(); } + await this.page + .getByRole('link', { + name: 'Add Injected Application Credentials', + exact: true, + }) + .click(); await this.page .getByRole('cell', { name: credentialName }) diff --git a/e2e-tests/admin/pages/users.js b/e2e-tests/admin/pages/users.js index f833e0d1a6..60ee8406b0 100644 --- a/e2e-tests/admin/pages/users.js +++ b/e2e-tests/admin/pages/users.js @@ -43,7 +43,6 @@ export class UsersPage extends BaseResourcePage { .getByRole('link', { name: 'Accounts', exact: true }) .click(); await this.page - .getByRole('article') .getByRole('link', { name: 'Add Accounts', exact: true }) .click(); await this.page diff --git a/e2e-tests/admin/playwright.config.js b/e2e-tests/admin/playwright.config.js index e6c2b6b3b1..4e302ae3a5 100644 --- a/e2e-tests/admin/playwright.config.js +++ b/e2e-tests/admin/playwright.config.js @@ -3,22 +3,24 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { defineConfig, devices, test as baseTest } from '@playwright/test'; +import { defineConfig, devices } from '@playwright/test'; +import { authenticatedState } from '../global-setup'; /** @type {import('@playwright/test').PlaywrightTestConfig} */ export default defineConfig({ - globalSetup: './global-setup', + globalSetup: '../global-setup', outputDir: './artifacts/test-failures', timeout: 90000, // Each test is given 90s to complete workers: 1, // Tests need to be run in serial, otherwise there may be conflicts when using the CLI use: { baseURL: process.env.BOUNDARY_ADDR, - trace: 'retain-on-failure', - screenshot: 'only-on-failure', extraHTTPHeaders: { // This token is set in global-setup.js Authorization: `Bearer ${process.env.E2E_TOKEN}`, }, + screenshot: 'only-on-failure', + storageState: authenticatedState, + trace: 'retain-on-failure', }, projects: [ { @@ -35,35 +37,3 @@ export default defineConfig({ }, ], }); - -export const test = baseTest.extend({ - adminAuthMethodId: process.env.E2E_PASSWORD_AUTH_METHOD_ID, - adminLoginName: process.env.E2E_PASSWORD_ADMIN_LOGIN_NAME, - adminPassword: process.env.E2E_PASSWORD_ADMIN_PASSWORD, - awsAccessKeyId: process.env.E2E_AWS_ACCESS_KEY_ID, - awsBucketName: process.env.E2E_AWS_BUCKET_NAME, - awsHostSetFilter: process.env.E2E_AWS_HOST_SET_FILTER, - awsHostSetIps: process.env.E2E_AWS_HOST_SET_IPS, - awsRegion: process.env.E2E_AWS_REGION, - awsSecretAccessKey: process.env.E2E_AWS_SECRET_ACCESS_KEY, - bucketAccessKeyId: process.env.E2E_BUCKET_ACCESS_KEY_ID, - bucketEndpointUrl: process.env.E2E_BUCKET_ENDPOINT_URL, - bucketName: process.env.E2E_BUCKET_NAME, - bucketSecretAccessKey: process.env.E2E_BUCKET_SECRET_ACCESS_KEY, - ldapAddr: process.env.E2E_LDAP_ADDR, - ldapAdminDn: process.env.E2E_LDAP_ADMIN_DN, - ldapAdminPassword: process.env.E2E_LDAP_ADMIN_PASSWORD, - ldapDomainDn: process.env.E2E_LDAP_DOMAIN_DN, - ldapGroupName: process.env.E2E_LDAP_GROUP_NAME, - ldapUserName: process.env.E2E_LDAP_USER_NAME, - ldapUserPassword: process.env.E2E_LDAP_USER_PASSWORD, - region: process.env.E2E_REGION, - sshCaKey: process.env.E2E_SSH_CA_KEY, - sshCaKeyPublic: process.env.E2E_SSH_CA_KEY_PUBLIC, - sshKeyPath: process.env.E2E_SSH_KEY_PATH, - sshUser: process.env.E2E_SSH_USER, - targetAddress: process.env.E2E_TARGET_ADDRESS, - targetPort: process.env.E2E_TARGET_PORT, - vaultAddr: process.env.E2E_VAULT_ADDR, - workerTagEgress: process.env.E2E_WORKER_TAG_EGRESS, -}); diff --git a/e2e-tests/admin/tests/alias-ent.spec.js b/e2e-tests/admin/tests/alias-ent.spec.js index cb723b7e9b..8c6fa99e33 100644 --- a/e2e-tests/admin/tests/alias-ent.spec.js +++ b/e2e-tests/admin/tests/alias-ent.spec.js @@ -3,31 +3,20 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { test } from '../playwright.config.js'; +import { test } from '../../global-setup.js'; import { expect } from '@playwright/test'; import { customAlphabet } from 'nanoid'; -import { authenticatedState } from '../global-setup.js'; -import { - authenticateBoundaryCli, - authorizeSessionByAliasCli, - checkBoundaryCli, - deleteAliasCli, - deleteScopeCli, - getOrgIdFromNameCli, - getProjectIdFromNameCli, - getTargetIdFromNameCli, -} from '../../helpers/boundary-cli.js'; +import * as boundaryCli from '../../helpers/boundary-cli'; import { AliasesPage } from '../pages/aliases.js'; import { CredentialStoresPage } from '../pages/credential-stores.js'; import { OrgsPage } from '../pages/orgs.js'; import { ProjectsPage } from '../pages/projects.js'; +import { SessionsPage } from '../pages/sessions.js'; import { TargetsPage } from '../pages/targets.js'; -test.use({ storageState: authenticatedState }); - test.beforeAll(async () => { - await checkBoundaryCli(); + await boundaryCli.checkBoundaryCli(); }); test.describe('Aliases (Enterprise)', async () => { @@ -45,6 +34,7 @@ test.describe('Aliases (Enterprise)', async () => { await page.goto('/'); let orgName; let alias; + let connect; const nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz', 10); try { const orgsPage = new OrgsPage(page); @@ -87,15 +77,22 @@ test.describe('Aliases (Enterprise)', async () => { await page.getByRole('button', { name: 'Dismiss' }).click(); // Connect to target using alias - await authenticateBoundaryCli( + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, adminPassword, ); - await authorizeSessionByAliasCli(alias); + connect = await boundaryCli.connectSshToAlias(alias); + const sessionsPage = new SessionsPage(page); + await sessionsPage.waitForSessionToBeVisible(targetName); // Clear destination from alias + await page + .getByRole('navigation', { name: 'Resources' }) + .getByRole('link', { name: 'Targets' }) + .click(); + await page.getByRole('link', { name: targetName }).click(); await page.getByRole('link', { name: alias }).click(); // Note: On the Target details page, there is a section with the header // "Aliases". The extra check here is to ensure that we are on the Alias @@ -111,20 +108,23 @@ test.describe('Aliases (Enterprise)', async () => { ).toBeVisible(); await page.getByRole('button', { name: 'Dismiss' }).click(); } finally { - await authenticateBoundaryCli( + if (connect) { + connect.kill('SIGTERM'); + } + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, adminPassword, ); if (orgName) { - const orgId = await getOrgIdFromNameCli(orgName); + const orgId = await boundaryCli.getOrgIdFromName(orgName); if (orgId) { - await deleteScopeCli(orgId); + await boundaryCli.deleteScope(orgId); } } if (alias) { - await deleteAliasCli(alias); + await boundaryCli.deleteAlias(alias); } } }); @@ -143,11 +143,12 @@ test.describe('Aliases (Enterprise)', async () => { await page.goto('/'); let orgName; let alias; + let connect; const nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz', 10); try { const orgsPage = new OrgsPage(page); orgName = await orgsPage.createOrg(); - await authenticateBoundaryCli( + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, @@ -175,9 +176,14 @@ test.describe('Aliases (Enterprise)', async () => { ); // Connect to target using alias - await authorizeSessionByAliasCli(alias); + connect = await boundaryCli.connectSshToAlias(alias); + const sessionsPage = new SessionsPage(page); + await sessionsPage.waitForSessionToBeVisible(targetName); } finally { - await authenticateBoundaryCli( + if (connect) { + connect.kill('SIGTERM'); + } + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, @@ -185,12 +191,12 @@ test.describe('Aliases (Enterprise)', async () => { ); if (orgName) { - const orgId = await getOrgIdFromNameCli(orgName); + const orgId = await boundaryCli.getOrgIdFromName(orgName); if (orgId) { - await deleteScopeCli(orgId); + await boundaryCli.deleteScope(orgId); } if (alias) { - await deleteAliasCli(alias); + await boundaryCli.deleteAlias(alias); } } } @@ -211,6 +217,7 @@ test.describe('Aliases (Enterprise)', async () => { const nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz', 10); let orgId; let alias; + let connect; try { const orgsPage = new OrgsPage(page); const orgName = await orgsPage.createOrg(); @@ -234,22 +241,39 @@ test.describe('Aliases (Enterprise)', async () => { ); // Create new alias from scope page - await authenticateBoundaryCli( + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, adminPassword, ); - orgId = await getOrgIdFromNameCli(orgName); - const projectId = await getProjectIdFromNameCli(orgId, projectName); - const targetId = await getTargetIdFromNameCli(projectId, targetName); + orgId = await boundaryCli.getOrgIdFromName(orgName); + const projectId = await boundaryCli.getProjectIdFromName( + orgId, + projectName, + ); + const targetId = await boundaryCli.getTargetIdFromName( + projectId, + targetName, + ); alias = 'example.alias.' + nanoid(); const aliasesPage = new AliasesPage(page); await aliasesPage.createAliasForTarget(alias, targetId); - await authorizeSessionByAliasCli(alias); + connect = await boundaryCli.connectSshToAlias(alias); + await page + .getByRole('navigation', { name: 'General' }) + .getByRole('link', { name: 'Orgs' }) + .click(); + await page.getByRole('link', { name: orgName }).click(); + await page.getByRole('link', { name: projectName }).click(); + const sessionsPage = new SessionsPage(page); + await sessionsPage.waitForSessionToBeVisible(targetName); } finally { - await authenticateBoundaryCli( + if (connect) { + connect.kill('SIGTERM'); + } + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, @@ -257,10 +281,10 @@ test.describe('Aliases (Enterprise)', async () => { ); if (orgId) { - await deleteScopeCli(orgId); + await boundaryCli.deleteScope(orgId); } if (alias) { - await deleteAliasCli(alias); + await boundaryCli.deleteAlias(alias); } } }); diff --git a/e2e-tests/admin/tests/alias.spec.js b/e2e-tests/admin/tests/alias.spec.js index 1e0c041d3a..6fa355d3c2 100644 --- a/e2e-tests/admin/tests/alias.spec.js +++ b/e2e-tests/admin/tests/alias.spec.js @@ -3,30 +3,19 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { test } from '../playwright.config.js'; +import { test } from '../../global-setup.js'; import { expect } from '@playwright/test'; import { customAlphabet } from 'nanoid'; -import { authenticatedState } from '../global-setup.js'; -import { - authenticateBoundaryCli, - authorizeSessionByAliasCli, - checkBoundaryCli, - deleteAliasCli, - deleteScopeCli, - getOrgIdFromNameCli, - getProjectIdFromNameCli, - getTargetIdFromNameCli, -} from '../../helpers/boundary-cli.js'; +import * as boundaryCli from '../../helpers/boundary-cli'; import { AliasesPage } from '../pages/aliases.js'; import { OrgsPage } from '../pages/orgs.js'; import { ProjectsPage } from '../pages/projects.js'; +import { SessionsPage } from '../pages/sessions.js'; import { TargetsPage } from '../pages/targets.js'; -test.use({ storageState: authenticatedState }); - test.beforeAll(async () => { - await checkBoundaryCli(); + await boundaryCli.checkBoundaryCli(); }); test.describe('Aliases', async () => { @@ -38,10 +27,13 @@ test.describe('Aliases', async () => { adminPassword, targetAddress, targetPort, + sshUser, + sshKeyPath, }) => { await page.goto('/'); let orgName; let alias; + let connect; const nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz', 10); try { const orgsPage = new OrgsPage(page); @@ -49,7 +41,10 @@ test.describe('Aliases', async () => { const projectsPage = new ProjectsPage(page); await projectsPage.createProject(); const targetsPage = new TargetsPage(page); - await targetsPage.createTargetWithAddress(targetAddress, targetPort); + const targetName = await targetsPage.createTargetWithAddress( + targetAddress, + targetPort, + ); // Create alias for target const aliasName = 'Alias ' + nanoid(); @@ -65,15 +60,22 @@ test.describe('Aliases', async () => { await page.getByRole('button', { name: 'Dismiss' }).click(); // Connect to target using alias - await authenticateBoundaryCli( + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, adminPassword, ); - await authorizeSessionByAliasCli(alias); + connect = await boundaryCli.connectToAlias(alias, sshUser, sshKeyPath); + const sessionsPage = new SessionsPage(page); + await sessionsPage.waitForSessionToBeVisible(targetName); // Clear destination from alias + await page + .getByRole('navigation', { name: 'Resources' }) + .getByRole('link', { name: 'Targets' }) + .click(); + await page.getByRole('link', { name: targetName }).click(); await page.getByRole('link', { name: alias }).click(); // Note: On the Target details page, there is a section with the header // "Aliases". The extra check here is to ensure that we are on the Alias @@ -89,20 +91,23 @@ test.describe('Aliases', async () => { ).toBeVisible(); await page.getByRole('button', { name: 'Dismiss' }).click(); } finally { - await authenticateBoundaryCli( + if (connect) { + connect.kill('SIGTERM'); + } + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, adminPassword, ); if (orgName) { - const orgId = await getOrgIdFromNameCli(orgName); + const orgId = await boundaryCli.getOrgIdFromName(orgName); if (orgId) { - await deleteScopeCli(orgId); + await boundaryCli.deleteScope(orgId); } } if (alias) { - await deleteAliasCli(alias); + await boundaryCli.deleteAlias(alias); } } }); @@ -115,15 +120,18 @@ test.describe('Aliases', async () => { adminPassword, targetAddress, targetPort, + sshUser, + sshKeyPath, }) => { await page.goto('/'); let orgName; let alias; + let connect; const nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz', 10); try { const orgsPage = new OrgsPage(page); orgName = await orgsPage.createOrg(); - await authenticateBoundaryCli( + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, @@ -133,28 +141,33 @@ test.describe('Aliases', async () => { await projectsPage.createProject(); alias = 'example.alias.' + nanoid(); const targetsPage = new TargetsPage(page); - await targetsPage.createTargetWithAddressAndAlias( + const targetName = await targetsPage.createTargetWithAddressAndAlias( targetAddress, targetPort, alias, ); // Connect to target using alias - await authorizeSessionByAliasCli(alias); + connect = await boundaryCli.connectToAlias(alias, sshUser, sshKeyPath); + const sessionsPage = new SessionsPage(page); + await sessionsPage.waitForSessionToBeVisible(targetName); } finally { - await authenticateBoundaryCli( + if (connect) { + connect.kill('SIGTERM'); + } + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, adminPassword, ); if (orgName) { - const orgId = await getOrgIdFromNameCli(orgName); + const orgId = await boundaryCli.getOrgIdFromName(orgName); if (orgId) { - await deleteScopeCli(orgId); + await boundaryCli.deleteScope(orgId); } if (alias) { - await deleteAliasCli(alias); + await boundaryCli.deleteAlias(alias); } } } @@ -168,11 +181,14 @@ test.describe('Aliases', async () => { adminPassword, targetAddress, targetPort, + sshUser, + sshKeyPath, }) => { await page.goto('/'); const nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz', 10); let orgId; let alias; + let connect; try { const orgsPage = new OrgsPage(page); const orgName = await orgsPage.createOrg(); @@ -185,32 +201,48 @@ test.describe('Aliases', async () => { ); // Create new alias from scope page - await authenticateBoundaryCli( + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, adminPassword, ); - orgId = await getOrgIdFromNameCli(orgName); - const projectId = await getProjectIdFromNameCli(orgId, projectName); - const targetId = await getTargetIdFromNameCli(projectId, targetName); + orgId = await boundaryCli.getOrgIdFromName(orgName); + const projectId = await boundaryCli.getProjectIdFromName( + orgId, + projectName, + ); + const targetId = await boundaryCli.getTargetIdFromName( + projectId, + targetName, + ); alias = 'example.alias.' + nanoid(); const aliasesPage = new AliasesPage(page); await aliasesPage.createAliasForTarget(alias, targetId); - await authorizeSessionByAliasCli(alias); + await page.getByRole('link', { name: 'Orgs', exact: true }).click(); + await expect(page.getByRole('heading', { name: 'Orgs' })).toBeVisible(); + await page.getByRole('link', { name: orgName }).click(); + await page.getByRole('link', { name: projectName }).click(); + + connect = await boundaryCli.connectToAlias(alias, sshUser, sshKeyPath); + const sessionsPage = new SessionsPage(page); + await sessionsPage.waitForSessionToBeVisible(targetName); } finally { - await authenticateBoundaryCli( + if (connect) { + connect.kill('SIGTERM'); + } + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, adminPassword, ); if (orgId) { - await deleteScopeCli(orgId); + await boundaryCli.deleteScope(orgId); } if (alias) { - await deleteAliasCli(alias); + await boundaryCli.deleteAlias(alias); } } }); diff --git a/e2e-tests/admin/tests/auth-method-ldap.spec.js b/e2e-tests/admin/tests/auth-method-ldap.spec.js index 60e6b22a04..3614facb37 100644 --- a/e2e-tests/admin/tests/auth-method-ldap.spec.js +++ b/e2e-tests/admin/tests/auth-method-ldap.spec.js @@ -3,24 +3,22 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { test } from '../playwright.config.js'; +import { test } from '../../global-setup.js'; import { expect } from '@playwright/test'; import { nanoid } from 'nanoid'; -import { - authenticateBoundaryCli, - checkBoundaryCli, - deleteScopeCli, - getOrgIdFromNameCli, -} from '../../helpers/boundary-cli.js'; +import * as boundaryCli from '../../helpers/boundary-cli'; import { AuthMethodsPage } from '../pages/auth-methods.js'; import { LoginPage } from '../pages/login.js'; import { OrgsPage } from '../pages/orgs.js'; import { RolesPage } from '../pages/roles.js'; import { UsersPage } from '../pages/users.js'; +// Reset storage state for this file to avoid being authenticated +test.use({ storageState: { cookies: [], origins: [] } }); + test.beforeAll(async () => { - await checkBoundaryCli(); + await boundaryCli.checkBoundaryCli(); }); test('Set up LDAP auth method @ce @ent @docker', async ({ @@ -111,8 +109,8 @@ test('Set up LDAP auth method @ce @ent @docker', async ({ ).toBeVisible(); // Change state to active-public - page.getByTitle('Inactive').click(); - page.getByText('Public').click(); + await page.getByTitle('Inactive').click(); + await page.getByText('Public').click(); await expect( page.getByRole('alert').getByText('Success', { exact: true }), ).toBeVisible(); @@ -195,8 +193,13 @@ test('Set up LDAP auth method @ce @ent @docker', async ({ .click(); await page.getByRole('link', { name: ldapAuthMethodName }).click(); await page.getByRole('link', { name: 'Accounts' }).click(); + await expect( + page + .getByRole('navigation', { name: 'breadcrumbs' }) + .getByText('Accounts'), + ).toBeVisible(); - const headersCount = await page + let headersCount = await page .getByRole('table') .getByRole('columnheader') .count(); @@ -216,21 +219,61 @@ test('Set up LDAP auth method @ce @ent @docker', async ({ } expect( - await page + page .getByRole('cell', { name: ldapAccountName }) .locator('..') .getByRole('cell') - .nth(fullNameIndex) - .innerText(), - ).toBe(ldapUserName); + .nth(fullNameIndex), + ).toHaveText(ldapUserName); expect( - await page + page .getByRole('cell', { name: ldapAccountName }) .locator('..') .getByRole('cell') - .nth(emailIndex) - .innerText(), - ).toBe(ldapUserName + '@mail.com'); + .nth(emailIndex), + ).toHaveText(ldapUserName + '@mail.com'); + + // View the Managed Group + await page.getByRole('link', { name: 'Managed Groups' }).click(); + await page.getByRole('link', { name: ldapManagedGroupName }).click(); + await page.getByRole('link', { name: 'Members' }).click(); + await expect( + page + .getByRole('navigation', { name: 'breadcrumbs' }) + .getByText('Members'), + ).toBeVisible(); + + headersCount = await page + .getByRole('table') + .getByRole('columnheader') + .count(); + for (let i = 0; i < headersCount; i++) { + const header = await page + .getByRole('table') + .getByRole('columnheader') + .nth(i) + .innerText(); + if (header == 'Full Name') { + fullNameIndex = i; + } else if (header == 'Email') { + emailIndex = i; + } + } + + expect( + page + .getByRole('cell', { name: ldapAccountName }) + .locator('..') + .getByRole('cell') + .nth(fullNameIndex), + ).toHaveText(ldapUserName); + expect( + page + .getByRole('cell', { name: ldapAccountName }) + .locator('..') + .getByRole('cell') + .nth(emailIndex), + ).toHaveText(ldapUserName + '@mail.com'); // View the User account and verify attributes await page @@ -250,15 +293,15 @@ test('Set up LDAP auth method @ce @ent @docker', async ({ ).toContain(ldapUserName + '@mail.com'); } finally { if (orgName) { - await authenticateBoundaryCli( + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, adminPassword, ); - const orgId = await getOrgIdFromNameCli(orgName); + const orgId = await boundaryCli.getOrgIdFromName(orgName); if (orgId) { - await deleteScopeCli(orgId); + await boundaryCli.deleteScope(orgId); } } } diff --git a/e2e-tests/admin/tests/auth-method-oidc-vault.spec.js b/e2e-tests/admin/tests/auth-method-oidc-vault.spec.js new file mode 100644 index 0000000000..fc7907c1d3 --- /dev/null +++ b/e2e-tests/admin/tests/auth-method-oidc-vault.spec.js @@ -0,0 +1,264 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { test } from '../../global-setup.js'; +import { expect } from '@playwright/test'; +import { execSync } from 'child_process'; + +import * as boundaryCli from '../../helpers/boundary-cli'; +import * as vaultCli from '../../helpers/vault-cli'; +import { AuthMethodsPage } from '../pages/auth-methods.js'; +import { LoginPage } from '../pages/login.js'; +import { OrgsPage } from '../pages/orgs.js'; +import { RolesPage } from '../pages/roles.js'; + +// Reset storage state for this file to avoid being authenticated +test.use({ storageState: { cookies: [], origins: [] } }); + +test.beforeAll(async () => { + await boundaryCli.checkBoundaryCli(); + await vaultCli.checkVaultCli(); +}); + +test.beforeEach(async () => { + execSync(`vault auth disable userpass`); +}); + +test('Set up OIDC auth method @ce @ent @docker @aws', async ({ + page, + context, + baseURL, + adminAuthMethodId, + adminLoginName, + adminPassword, + vaultAddr, +}) => { + await page.goto('/'); + let orgName; + let policyName; + try { + const userName = 'end-user'; + const password = 'password123'; + const email = 'vault@hashicorp.com'; + const { issuer, clientId, clientSecret, authPolicyName } = + await vaultCli.setupVaultOidc( + vaultAddr, + userName, + password, + email, + baseURL, + ); + policyName = authPolicyName; + + // Log in + const loginPage = new LoginPage(page); + await loginPage.login(adminLoginName, adminPassword); + await expect( + page.getByRole('navigation', { name: 'breadcrumbs' }).getByText('Orgs'), + ).toBeVisible(); + + // Create OIDC Auth Method + const orgsPage = new OrgsPage(page); + orgName = await orgsPage.createOrg(); + const authMethodsPage = new AuthMethodsPage(page); + const oidcAuthMethodName = await authMethodsPage.createOidcAuthMethod( + issuer, + clientId, + clientSecret, + baseURL, + ); + + // Change OIDC Auth Method state to active-public + await page.getByTitle('Inactive').click(); + await page.getByText('Public').click(); + await expect( + page.getByRole('alert').getByText('Success', { exact: true }), + ).toBeVisible(); + await page.getByRole('button', { name: 'Dismiss' }).click(); + + // Set auth method as primary + await page.getByText('Manage', { exact: true }).click(); + await page.getByRole('button', { name: 'Make Primary' }).click(); + await page.getByRole('button', { name: 'OK' }).click(); + await expect( + page.getByRole('alert').getByText('Success', { exact: true }), + ).toBeVisible(); + await page.getByRole('button', { name: 'Dismiss' }).click(); + + // Create an OIDC managed group + await page + .getByRole('navigation', { name: 'breadcrumbs' }) + .getByText(oidcAuthMethodName) + .click(); + await page.getByText('Manage', { exact: true }).click(); + await page.getByRole('link', { name: 'Create Managed Group' }).click(); + const oidcManagedGroupName = 'OIDC Managed Group'; + await page.getByLabel('Name (Optional)').fill(oidcManagedGroupName); + await page.getByLabel('Description').fill('This is an automated test'); + await page.getByLabel('Filter').fill(`"engineering" in "/userinfo/groups"`); + await page.getByRole('button', { name: 'Save' }).click(); + await expect( + page.getByRole('alert').getByText('Success', { exact: true }), + ).toBeVisible(); + await page.getByRole('button', { name: 'Dismiss' }).click(); + + // Create a role and add LDAP managed group to role + const rolesPage = new RolesPage(page); + await rolesPage.createRole(); + await rolesPage.addPrincipalToRole(oidcManagedGroupName); + + // Log in using oidc account + await loginPage.logout(adminLoginName); + await page.getByText('Choose a different scope').click(); + await page.getByRole('link', { name: orgName }).click(); + await page.getByRole('link', { name: oidcAuthMethodName }).click(); + const pagePromise = context.waitForEvent('page'); + await page.getByRole('button', { name: 'Sign In' }).click(); + const vaultPage = await pagePromise; + await vaultPage.getByLabel('Method').selectOption('Username'); + await vaultPage.getByLabel('Username').fill(userName); + await vaultPage.getByLabel('Password').fill(password); + await vaultPage.getByRole('button', { name: 'Sign In' }).click(); + await expect( + page + .getByRole('navigation', { name: 'breadcrumbs' }) + .getByText('Projects'), + ).toBeVisible(); + + // Log back in as an admin + // WORKAROUND: Currently, users logging using OIDC don't have a username + // displayed in the UI, so there's no simple locator to access this menu. + await page.locator('details').filter({ hasText: 'Sign Out' }).click(); + await page.getByRole('button', { name: 'Sign Out' }).click(); + await expect(page.getByRole('button', { name: 'Sign In' })).toBeVisible(); + await loginPage.login(adminLoginName, adminPassword); + await expect( + page.getByRole('navigation', { name: 'breadcrumbs' }).getByText('Orgs'), + ).toBeVisible(); + + // View the OIDC account and verify account attributes + await page.getByRole('link', { name: orgName }).click(); + await page + .getByRole('navigation', { name: 'IAM' }) + .getByRole('link', { name: 'Auth Methods' }) + .click(); + await page.getByRole('link', { name: oidcAuthMethodName }).click(); + await page.getByRole('link', { name: 'Accounts' }).click(); + await expect( + page + .getByRole('navigation', { name: 'breadcrumbs' }) + .getByText('Accounts'), + ).toBeVisible(); + + let headersCount = await page + .getByRole('table') + .getByRole('columnheader') + .count(); + let fullNameIndex; + let emailIndex; + for (let i = 0; i < headersCount; i++) { + const header = await page + .getByRole('table') + .getByRole('columnheader') + .nth(i) + .innerText(); + if (header == 'Full Name') { + fullNameIndex = i; + } else if (header == 'Email') { + emailIndex = i; + } + } + + expect( + page + .getByRole('cell', { name: userName }) + .locator('..') + .getByRole('cell') + .nth(fullNameIndex), + ).toHaveText(userName); + expect( + page + .getByRole('cell', { name: userName }) + .locator('..') + .getByRole('cell') + .nth(emailIndex), + ).toHaveText(email); + + // View the OIDC Managed Group and verify member in managed group + await page.getByRole('link', { name: 'Managed Groups' }).click(); + await page.getByRole('link', { name: oidcManagedGroupName }).click(); + await page.getByRole('link', { name: 'Members' }).click(); + await expect( + page + .getByRole('navigation', { name: 'breadcrumbs' }) + .getByText('Members'), + ).toBeVisible(); + + headersCount = await page + .getByRole('table') + .getByRole('columnheader') + .count(); + for (let i = 0; i < headersCount; i++) { + const header = await page + .getByRole('table') + .getByRole('columnheader') + .nth(i) + .innerText(); + if (header == 'Full Name') { + fullNameIndex = i; + } else if (header == 'Email') { + emailIndex = i; + } + } + + expect( + page + .getByRole('cell', { name: userName }) + .locator('..') + .getByRole('cell') + .nth(fullNameIndex), + ).toHaveText(userName); + expect( + page + .getByRole('cell', { name: userName }) + .locator('..') + .getByRole('cell') + .nth(emailIndex), + ).toHaveText(email); + + // View the User account and verify attributes + await page + .getByRole('navigation', { name: 'IAM' }) + .getByRole('link', { name: 'Users' }) + .click(); + await page.getByRole('cell', { hasText: email }).getByRole('link').click(); + await page.getByRole('link', { name: 'Accounts' }).click(); + expect( + await page + .getByRole('table') + .getByRole('row') + .nth(1) // Account row + .getByRole('cell') + .nth(0) // Name field + .innerText(), + ).toContain(email); + } finally { + execSync(`vault auth disable userpass`); + execSync(`vault policy delete ${policyName}`); + + if (orgName) { + await boundaryCli.authenticateBoundary( + baseURL, + adminAuthMethodId, + adminLoginName, + adminPassword, + ); + const orgId = await boundaryCli.getOrgIdFromName(orgName); + if (orgId) { + await boundaryCli.deleteScope(orgId); + } + } + } +}); diff --git a/e2e-tests/admin/tests/auth-method-password.spec.js b/e2e-tests/admin/tests/auth-method-password.spec.js index 8604dbcb2a..a4db707e9a 100644 --- a/e2e-tests/admin/tests/auth-method-password.spec.js +++ b/e2e-tests/admin/tests/auth-method-password.spec.js @@ -3,23 +3,21 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { test } from '../playwright.config.js'; +import { test } from '../../global-setup.js'; import { expect } from '@playwright/test'; import { execSync } from 'node:child_process'; -import { - authenticateBoundaryCli, - checkBoundaryCli, - deleteScopeCli, - getOrgIdFromNameCli, -} from '../../helpers/boundary-cli.js'; +import * as boundaryCli from '../../helpers/boundary-cli'; import { AuthMethodsPage } from '../pages/auth-methods.js'; import { LoginPage } from '../pages/login.js'; import { OrgsPage } from '../pages/orgs.js'; import { UsersPage } from '../pages/users.js'; +// Reset storage state for this file to avoid being authenticated +test.use({ storageState: { cookies: [], origins: [] } }); + test.beforeAll(async () => { - await checkBoundaryCli(); + await boundaryCli.checkBoundaryCli(); }); test('Verify new auth-method can be created and assigned to users @ce @ent @aws @docker', async ({ @@ -104,7 +102,7 @@ test('Verify new auth-method can be created and assigned to users @ce @ent @aws .getByText('Projects'), ).toBeVisible(); } finally { - await authenticateBoundaryCli( + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, @@ -130,9 +128,9 @@ test('Verify new auth-method can be created and assigned to users @ce @ent @aws } if (orgName) { - const orgId = await getOrgIdFromNameCli(orgName); + const orgId = await boundaryCli.getOrgIdFromName(orgName); if (orgId) { - await deleteScopeCli(orgId); + await boundaryCli.deleteScope(orgId); } } } diff --git a/e2e-tests/admin/tests/credential-store-static-ent.spec.js b/e2e-tests/admin/tests/credential-store-static-ent.spec.js index 6d0ae606fc..6579bdaee6 100644 --- a/e2e-tests/admin/tests/credential-store-static-ent.spec.js +++ b/e2e-tests/admin/tests/credential-store-static-ent.spec.js @@ -3,25 +3,19 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { test } from '../playwright.config.js'; +import { test } from '../../global-setup.js'; import { expect } from '@playwright/test'; +import { readFile } from 'fs/promises'; -import { authenticatedState } from '../global-setup.js'; -import { - authenticateBoundaryCli, - checkBoundaryCli, - deleteScopeCli, - getOrgIdFromNameCli, -} from '../../helpers/boundary-cli.js'; +import * as boundaryCli from '../../helpers/boundary-cli'; import { CredentialStoresPage } from '../pages/credential-stores.js'; import { OrgsPage } from '../pages/orgs.js'; import { ProjectsPage } from '../pages/projects.js'; +import { SessionsPage } from '../pages/sessions.js'; import { TargetsPage } from '../pages/targets.js'; -test.use({ storageState: authenticatedState }); - test.beforeAll(async () => { - await checkBoundaryCli(); + await boundaryCli.checkBoundaryCli(); }); test('Multiple Credential Stores (ENT) @ent @aws @docker', async ({ @@ -42,7 +36,7 @@ test('Multiple Credential Stores (ENT) @ent @aws @docker', async ({ const orgsPage = new OrgsPage(page); orgName = await orgsPage.createOrg(); const projectsPage = new ProjectsPage(page); - await projectsPage.createProject(); + const projectName = await projectsPage.createProject(); const targetsPage = new TargetsPage(page); const targetName = await targetsPage.createSshTargetWithAddressEnt( targetAddress, @@ -106,17 +100,47 @@ test('Multiple Credential Stores (ENT) @ent @aws @docker', async ({ page.getByRole('alert').getByText('Success', { exact: true }), ).toBeVisible(); await page.getByRole('button', { name: 'Dismiss' }).click(); + + // Verify credentials + await boundaryCli.authenticateBoundary( + baseURL, + adminAuthMethodId, + adminLoginName, + adminPassword, + ); + const orgId = await boundaryCli.getOrgIdFromName(orgName); + const projectId = await boundaryCli.getProjectIdFromName( + orgId, + projectName, + ); + const targetId = await boundaryCli.getTargetIdFromName( + projectId, + targetName, + ); + const session = await boundaryCli.authorizeSessionByTargetId(targetId); + const retrievedUser = session.item.credentials[0].credential.username; + const retrievedKey = session.item.credentials[0].credential.private_key; + + expect(retrievedUser).toBe(sshUser); + + const keyData = await readFile(sshKeyPath, { + encoding: 'utf-8', + }); + expect(retrievedKey).toBe(keyData); + + const sessionsPage = new SessionsPage(page); + await sessionsPage.waitForSessionToBeVisible(targetName); } finally { if (orgName) { - await authenticateBoundaryCli( + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, adminPassword, ); - const orgId = await getOrgIdFromNameCli(orgName); + const orgId = await boundaryCli.getOrgIdFromName(orgName); if (orgId) { - await deleteScopeCli(orgId); + await boundaryCli.deleteScope(orgId); } } } diff --git a/e2e-tests/admin/tests/credential-store-static.spec.js b/e2e-tests/admin/tests/credential-store-static.spec.js index e5198696d9..c3dbf33c55 100644 --- a/e2e-tests/admin/tests/credential-store-static.spec.js +++ b/e2e-tests/admin/tests/credential-store-static.spec.js @@ -3,30 +3,20 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { test } from '../playwright.config.js'; +import { test } from '../../global-setup.js'; import { expect } from '@playwright/test'; import { readFile } from 'fs/promises'; import { nanoid } from 'nanoid'; -import { authenticatedState } from '../global-setup.js'; -import { - authenticateBoundaryCli, - authorizeSessionByTargetIdCli, - checkBoundaryCli, - deleteScopeCli, - getOrgIdFromNameCli, - getProjectIdFromNameCli, - getTargetIdFromNameCli, -} from '../../helpers/boundary-cli.js'; +import * as boundaryCli from '../../helpers/boundary-cli'; import { CredentialStoresPage } from '../pages/credential-stores.js'; import { OrgsPage } from '../pages/orgs.js'; import { ProjectsPage } from '../pages/projects.js'; +import { SessionsPage } from '../pages/sessions.js'; import { TargetsPage } from '../pages/targets.js'; -test.use({ storageState: authenticatedState }); - test.beforeAll(async () => { - await checkBoundaryCli(); + await boundaryCli.checkBoundaryCli(); }); let orgName; @@ -71,16 +61,22 @@ test('Static Credential Store (User & Key Pair) @ce @aws @docker', async ({ credentialName, ); - await authenticateBoundaryCli( + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, adminPassword, ); - const orgId = await getOrgIdFromNameCli(orgName); - const projectId = await getProjectIdFromNameCli(orgId, projectName); - const targetId = await getTargetIdFromNameCli(projectId, targetName); - const session = await authorizeSessionByTargetIdCli(targetId); + const orgId = await boundaryCli.getOrgIdFromName(orgName); + const projectId = await boundaryCli.getProjectIdFromName( + orgId, + projectName, + ); + const targetId = await boundaryCli.getTargetIdFromName( + projectId, + targetName, + ); + const session = await boundaryCli.authorizeSessionByTargetId(targetId); const retrievedUser = session.item.credentials[0].credential.username; const retrievedKey = session.item.credentials[0].credential.private_key; @@ -89,20 +85,21 @@ test('Static Credential Store (User & Key Pair) @ce @aws @docker', async ({ const keyData = await readFile(sshKeyPath, { encoding: 'utf-8', }); - if (keyData != retrievedKey) { - throw new Error('Stored Key does not match'); - } + expect(retrievedKey).toBe(keyData); + + const sessionsPage = new SessionsPage(page); + await sessionsPage.waitForSessionToBeVisible(targetName); } finally { if (orgName) { - await authenticateBoundaryCli( + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, adminPassword, ); - const orgId = await getOrgIdFromNameCli(orgName); + const orgId = await boundaryCli.getOrgIdFromName(orgName); if (orgId) { - await deleteScopeCli(orgId); + await boundaryCli.deleteScope(orgId); } } } @@ -130,32 +127,41 @@ test('Static Credential Store (Username & Password) @ce @aws @docker', async ({ credentialName, ); - await authenticateBoundaryCli( + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, adminPassword, ); - const orgId = await getOrgIdFromNameCli(orgName); - const projectId = await getProjectIdFromNameCli(orgId, projectName); - const targetId = await getTargetIdFromNameCli(projectId, targetName); - const session = await authorizeSessionByTargetIdCli(targetId); + const orgId = await boundaryCli.getOrgIdFromName(orgName); + const projectId = await boundaryCli.getProjectIdFromName( + orgId, + projectName, + ); + const targetId = await boundaryCli.getTargetIdFromName( + projectId, + targetName, + ); + const session = await boundaryCli.authorizeSessionByTargetId(targetId); const retrievedUser = session.item.credentials[0].credential.username; const retrievedPassword = session.item.credentials[0].credential.password; expect(retrievedUser).toBe(sshUser); expect(retrievedPassword).toBe(testPassword); + + const sessionsPage = new SessionsPage(page); + await sessionsPage.waitForSessionToBeVisible(targetName); } finally { if (orgName) { - await authenticateBoundaryCli( + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, adminPassword, ); - const orgId = await getOrgIdFromNameCli(orgName); + const orgId = await boundaryCli.getOrgIdFromName(orgName); if (orgId) { - await deleteScopeCli(orgId); + await boundaryCli.deleteScope(orgId); } } } @@ -201,16 +207,22 @@ test('Static Credential Store (JSON) @ce @aws @docker', async ({ credentialName, ); - await authenticateBoundaryCli( + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, adminPassword, ); - const orgId = await getOrgIdFromNameCli(orgName); - const projectId = await getProjectIdFromNameCli(orgId, projectName); - const targetId = await getTargetIdFromNameCli(projectId, targetName); - const session = await authorizeSessionByTargetIdCli(targetId); + const orgId = await boundaryCli.getOrgIdFromName(orgName); + const projectId = await boundaryCli.getProjectIdFromName( + orgId, + projectName, + ); + const targetId = await boundaryCli.getTargetIdFromName( + projectId, + targetName, + ); + const session = await boundaryCli.authorizeSessionByTargetId(targetId); const retrievedUser = session.item.credentials[0].credential.username; const retrievedPassword = session.item.credentials[0].credential.password; const retrievedId = session.item.credentials[0].credential.id; @@ -218,17 +230,20 @@ test('Static Credential Store (JSON) @ce @aws @docker', async ({ expect(retrievedUser).toBe(testName); expect(retrievedPassword).toBe(testPassword); expect(retrievedId).toBe(testId); + + const sessionsPage = new SessionsPage(page); + await sessionsPage.waitForSessionToBeVisible(targetName); } finally { if (orgName) { - await authenticateBoundaryCli( + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, adminPassword, ); - const orgId = await getOrgIdFromNameCli(orgName); + const orgId = await boundaryCli.getOrgIdFromName(orgName); if (orgId) { - await deleteScopeCli(orgId); + await boundaryCli.deleteScope(orgId); } } } @@ -281,15 +296,15 @@ test('Multiple Credential Stores (CE) @ce @aws @docker', async ({ await page.getByRole('button', { name: 'Dismiss' }).click(); } finally { if (orgName) { - await authenticateBoundaryCli( + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, adminPassword, ); - const orgId = await getOrgIdFromNameCli(orgName); + const orgId = await boundaryCli.getOrgIdFromName(orgName); if (orgId) { - await deleteScopeCli(orgId); + await boundaryCli.deleteScope(orgId); } } } diff --git a/e2e-tests/admin/tests/credential-store-vault-ent.spec.js b/e2e-tests/admin/tests/credential-store-vault-ent.spec.js index b4881f8045..00b218922b 100644 --- a/e2e-tests/admin/tests/credential-store-vault-ent.spec.js +++ b/e2e-tests/admin/tests/credential-store-vault-ent.spec.js @@ -3,23 +3,15 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { test } from '../playwright.config.js'; +import { test } from '../../global-setup.js'; import { execSync } from 'child_process'; -import { authenticatedState } from '../global-setup.js'; -import { - authenticateBoundaryCli, - authorizeSessionByTargetIdCli, - checkBoundaryCli, - deleteScopeCli, - getOrgIdFromNameCli, - getProjectIdFromNameCli, - getTargetIdFromNameCli, -} from '../../helpers/boundary-cli.js'; -import { checkVaultCli } from '../../helpers/vault-cli.js'; +import * as boundaryCli from '../../helpers/boundary-cli'; +import * as vaultCli from '../../helpers/vault-cli'; import { CredentialStoresPage } from '../pages/credential-stores.js'; import { OrgsPage } from '../pages/orgs.js'; import { ProjectsPage } from '../pages/projects.js'; +import { SessionsPage } from '../pages/sessions.js'; import { TargetsPage } from '../pages/targets.js'; const secretsPath = 'e2e_secrets'; @@ -27,11 +19,9 @@ const secretName = 'cred'; const secretPolicyName = 'kv-policy'; const boundaryPolicyName = 'boundary-controller'; -test.use({ storageState: authenticatedState }); - test.beforeAll(async () => { - await checkBoundaryCli(); - await checkVaultCli(); + await boundaryCli.checkBoundaryCli(); + await vaultCli.checkVaultCli(); }); test.beforeEach(async ({ page }) => { @@ -55,6 +45,7 @@ test('Vault Credential Store (User & Key Pair) @ent @aws @docker', async ({ vaultAddr, }) => { let orgId; + let connect; try { execSync( `vault policy write ${boundaryPolicyName} ./admin/tests/fixtures/boundary-controller-policy.hcl`, @@ -106,19 +97,32 @@ test('Vault Credential Store (User & Key Pair) @ent @aws @docker', async ({ credentialLibraryName, ); - await authenticateBoundaryCli( + // Verify that session can be established + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, adminPassword, ); - orgId = await getOrgIdFromNameCli(orgName); - const projectId = await getProjectIdFromNameCli(orgId, projectName); - const targetId = await getTargetIdFromNameCli(projectId, targetName); - await authorizeSessionByTargetIdCli(targetId); + orgId = await boundaryCli.getOrgIdFromName(orgName); + const projectId = await boundaryCli.getProjectIdFromName( + orgId, + projectName, + ); + const targetId = await boundaryCli.getTargetIdFromName( + projectId, + targetName, + ); + connect = await boundaryCli.connectSshToTarget(targetId); + const sessionsPage = new SessionsPage(page); + await sessionsPage.waitForSessionToBeVisible(targetName); } finally { + if (connect) { + connect.kill('SIGTERM'); + } + if (orgId) { - await deleteScopeCli(orgId); + await boundaryCli.deleteScope(orgId); } } }); diff --git a/e2e-tests/admin/tests/credential-store-vault.spec.js b/e2e-tests/admin/tests/credential-store-vault.spec.js index b2fa4f1fba..b848f7e1fc 100644 --- a/e2e-tests/admin/tests/credential-store-vault.spec.js +++ b/e2e-tests/admin/tests/credential-store-vault.spec.js @@ -3,26 +3,18 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { test } from '../playwright.config.js'; +import { test } from '../../global-setup.js'; import { expect } from '@playwright/test'; import { execSync } from 'child_process'; import { nanoid } from 'nanoid'; import { readFile } from 'fs/promises'; -import { authenticatedState } from '../global-setup.js'; -import { - authenticateBoundaryCli, - authorizeSessionByTargetIdCli, - checkBoundaryCli, - deleteScopeCli, - getOrgIdFromNameCli, - getProjectIdFromNameCli, - getTargetIdFromNameCli, -} from '../../helpers/boundary-cli.js'; -import { checkVaultCli } from '../../helpers/vault-cli.js'; +import * as boundaryCli from '../../helpers/boundary-cli'; +import * as vaultCli from '../../helpers/vault-cli'; import { CredentialStoresPage } from '../pages/credential-stores.js'; import { OrgsPage } from '../pages/orgs.js'; import { ProjectsPage } from '../pages/projects.js'; +import { SessionsPage } from '../pages/sessions.js'; import { TargetsPage } from '../pages/targets.js'; const secretsPath = 'e2e_secrets'; @@ -30,11 +22,9 @@ const secretName = 'cred'; const secretPolicyName = 'kv-policy'; const boundaryPolicyName = 'boundary-controller'; -test.use({ storageState: authenticatedState }); - test.beforeAll(async () => { - await checkBoundaryCli(); - await checkVaultCli(); + await boundaryCli.checkBoundaryCli(); + await vaultCli.checkVaultCli(); }); test.beforeEach(async ({ page }) => { @@ -123,32 +113,39 @@ test('Vault Credential Store (User & Key Pair) @ce @aws @docker', async ({ credentialLibraryName, ); - await authenticateBoundaryCli( + // Verify correct credentials are returned after authorizing session + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, adminPassword, ); - orgId = await getOrgIdFromNameCli(orgName); - const projectId = await getProjectIdFromNameCli(orgId, projectName); - const targetId = await getTargetIdFromNameCli(projectId, targetName); - const session = await authorizeSessionByTargetIdCli(targetId); + orgId = await boundaryCli.getOrgIdFromName(orgName); + const projectId = await boundaryCli.getProjectIdFromName( + orgId, + projectName, + ); + const targetId = await boundaryCli.getTargetIdFromName( + projectId, + targetName, + ); + const session = await boundaryCli.authorizeSessionByTargetId(targetId); const retrievedUser = session.item.credentials[0].secret.decoded.data.username; const retrievedKey = session.item.credentials[0].secret.decoded.data.private_key; expect(retrievedUser).toBe(sshUser); - const keyData = await readFile(sshKeyPath, { encoding: 'utf-8', }); - if (keyData !== retrievedKey) { - throw new Error('Stored Key does not match'); - } + expect(retrievedKey).toBe(keyData); + + const sessionsPage = new SessionsPage(page); + await sessionsPage.waitForSessionToBeVisible(targetName); } finally { if (orgId) { - await deleteScopeCli(orgId); + await boundaryCli.deleteScope(orgId); } } }); diff --git a/e2e-tests/admin/tests/delete-resources-ent.spec.js b/e2e-tests/admin/tests/delete-resources-ent.spec.js index aa373fd22c..3606a4441c 100644 --- a/e2e-tests/admin/tests/delete-resources-ent.spec.js +++ b/e2e-tests/admin/tests/delete-resources-ent.spec.js @@ -3,34 +3,11 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { test } from '../playwright.config.js'; +import { test } from '../../global-setup.js'; import { execSync } from 'node:child_process'; -import { authenticatedState } from '../global-setup.js'; -import { - authenticateBoundaryCli, - checkBoundaryCli, - createOrgCli, - createProjectCli, - createControllerLedWorkerCli, - createPasswordAuthMethodCli, - makeAuthMethodPrimaryCli, - createPasswordAccountCli, - createRoleCli, - createGroupCli, - createUserCli, - createStaticHostCatalogCli, - createDynamicAwsHostCatalogCli, - createStaticHostCli, - createHostSetCli, - createStaticCredentialStoreCli, - createTcpTarget, - createSshTargetCli, - createVaultCredentialStoreCli, - createUsernamePasswordCredentialCli, - deleteScopeCli, -} from '../../helpers/boundary-cli.js'; -import { checkVaultCli, getVaultToken } from '../../helpers/vault-cli.js'; +import * as boundaryCli from '../../helpers/boundary-cli'; +import * as vaultCli from '../../helpers/vault-cli'; import { AuthMethodsPage } from '../pages/auth-methods.js'; import { BaseResourcePage } from '../pages/base-resource.js'; import { WorkersPage } from '../pages/workers.js'; @@ -38,23 +15,21 @@ import { WorkersPage } from '../pages/workers.js'; const secretPolicyName = 'kv-policy'; const boundaryPolicyName = 'boundary-controller'; -test.use({ storageState: authenticatedState }); - // Setting the test timeout to 120s // This test can often exceed the globally defined timeout due to the number of // network calls. test.setTimeout(180000); test.beforeAll(async () => { - await checkBoundaryCli(); - await checkVaultCli(); + await boundaryCli.checkBoundaryCli(); + await vaultCli.checkVaultCli(); }); test.beforeEach( async ({ baseURL, adminAuthMethodId, adminLoginName, adminPassword }) => { execSync(`vault policy delete ${secretPolicyName}`); execSync(`vault policy delete ${boundaryPolicyName}`); - await authenticateBoundaryCli( + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, @@ -71,43 +46,47 @@ test('Verify resources can be deleted (enterprise) @ent @aws', async ({ let orgId; let orgDeleted = false; try { - orgId = await createOrgCli(); - let projectId = await createProjectCli(orgId); + orgId = await boundaryCli.createOrg(); + let projectId = await boundaryCli.createProject(orgId); // Create boundary resources using CLI - let workerId = await createControllerLedWorkerCli(); - let authMethodId = await createPasswordAuthMethodCli(orgId); - await makeAuthMethodPrimaryCli(orgId, authMethodId); - let passwordAccountId = await createPasswordAccountCli(authMethodId); - let projectScopeRoleId = await createRoleCli(projectId); - let orgScopeRoleId = await createRoleCli(orgId); - let globalScopeRoleId = await createRoleCli('global'); - let groupId = await createGroupCli(orgId); - let userId = await createUserCli(orgId); - let staticHostCatalogId = await createStaticHostCatalogCli(projectId); - let dynamicAwsHostCatalogId = await createDynamicAwsHostCatalogCli( + let workerId = await boundaryCli.createControllerLedWorker(); + let authMethodId = await boundaryCli.createPasswordAuthMethod(orgId); + await boundaryCli.makeAuthMethodPrimary(orgId, authMethodId); + let passwordAccountId = + await boundaryCli.createPasswordAccount(authMethodId); + let projectScopeRoleId = await boundaryCli.createRole(projectId); + let orgScopeRoleId = await boundaryCli.createRole(orgId); + let globalScopeRoleId = await boundaryCli.createRole('global'); + let groupId = await boundaryCli.createGroup(orgId); + let userId = await boundaryCli.createUser(orgId); + let staticHostCatalogId = + await boundaryCli.createStaticHostCatalog(projectId); + let dynamicAwsHostCatalogId = await boundaryCli.createDynamicAwsHostCatalog( projectId, awsRegion, ); - let staticHostId = await createStaticHostCli(staticHostCatalogId); - let staticHostSetId = await createHostSetCli(staticHostCatalogId); + let staticHostId = await boundaryCli.createStaticHost(staticHostCatalogId); + let staticHostSetId = await boundaryCli.createHostSet(staticHostCatalogId); let staticCredentialStoreId = - await createStaticCredentialStoreCli(projectId); - const vaultToken = await getVaultToken( + await boundaryCli.createStaticCredentialStore(projectId); + const vaultToken = await vaultCli.getVaultToken( boundaryPolicyName, secretPolicyName, ); - let vaultCredentialStoreId = await createVaultCredentialStoreCli( + let vaultCredentialStoreId = await boundaryCli.createVaultCredentialStore( projectId, vaultAddr, vaultToken, ); let usernamePasswordCredentialId = - await createUsernamePasswordCredentialCli(staticCredentialStoreId); - let tcpTargetId = await createTcpTarget(projectId); + await boundaryCli.createUsernamePasswordCredential( + staticCredentialStoreId, + ); + let tcpTargetId = await boundaryCli.createTcpTarget(projectId); // Create enterprise boundary resources using CLI - let sshTargetId = await createSshTargetCli(projectId); + let sshTargetId = await boundaryCli.createSshTarget(projectId); // Delete resources const baseResourcePage = new BaseResourcePage(page); @@ -176,7 +155,7 @@ test('Verify resources can be deleted (enterprise) @ent @aws', async ({ orgDeleted = true; } finally { if (orgId && orgDeleted == false) { - await deleteScopeCli(orgId); + await boundaryCli.deleteScope(orgId); } } }); diff --git a/e2e-tests/admin/tests/delete-resources.spec.js b/e2e-tests/admin/tests/delete-resources.spec.js index 63d8fd0f06..265fd53567 100644 --- a/e2e-tests/admin/tests/delete-resources.spec.js +++ b/e2e-tests/admin/tests/delete-resources.spec.js @@ -3,33 +3,11 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { test } from '../playwright.config.js'; +import { test } from '../../global-setup.js'; import { execSync } from 'node:child_process'; -import { authenticatedState } from '../global-setup.js'; -import { - authenticateBoundaryCli, - checkBoundaryCli, - createOrgCli, - createProjectCli, - createControllerLedWorkerCli, - createPasswordAuthMethodCli, - makeAuthMethodPrimaryCli, - createPasswordAccountCli, - createRoleCli, - createGroupCli, - createUserCli, - createStaticHostCatalogCli, - createDynamicAwsHostCatalogCli, - createStaticHostCli, - createHostSetCli, - createStaticCredentialStoreCli, - createTcpTarget, - createVaultCredentialStoreCli, - createUsernamePasswordCredentialCli, - deleteScopeCli, -} from '../../helpers/boundary-cli.js'; -import { checkVaultCli, getVaultToken } from '../../helpers/vault-cli.js'; +import * as boundaryCli from '../../helpers/boundary-cli'; +import * as vaultCli from '../../helpers/vault-cli'; import { AuthMethodsPage } from '../pages/auth-methods.js'; import { BaseResourcePage } from '../pages/base-resource.js'; import { WorkersPage } from '../pages/workers.js'; @@ -37,23 +15,21 @@ import { WorkersPage } from '../pages/workers.js'; const secretPolicyName = 'kv-policy'; const boundaryPolicyName = 'boundary-controller'; -test.use({ storageState: authenticatedState }); - // Setting the test timeout to 180s // This test can often exceed the globally defined timeout due to the number of // network calls. test.setTimeout(180000); test.beforeAll(async () => { - await checkBoundaryCli(); - await checkVaultCli(); + await boundaryCli.checkBoundaryCli(); + await vaultCli.checkVaultCli(); }); test.beforeEach( async ({ baseURL, adminAuthMethodId, adminLoginName, adminPassword }) => { execSync(`vault policy delete ${secretPolicyName}`); execSync(`vault policy delete ${boundaryPolicyName}`); - await authenticateBoundaryCli( + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, @@ -71,38 +47,42 @@ test('Verify resources can be deleted @ce @aws', async ({ let orgDeleted = false; try { // Create boundary resources using CLI - orgId = await createOrgCli(); - let projectId = await createProjectCli(orgId); - let workerId = await createControllerLedWorkerCli(); - let authMethodId = await createPasswordAuthMethodCli(orgId); - await makeAuthMethodPrimaryCli(orgId, authMethodId); - let passwordAccountId = await createPasswordAccountCli(authMethodId); - let projectScopeRoleId = await createRoleCli(projectId); - let orgScopeRoleId = await createRoleCli(orgId); - let globalScopeRoleId = await createRoleCli('global'); - let groupId = await createGroupCli(orgId); - let userId = await createUserCli(orgId); - let staticHostCatalogId = await createStaticHostCatalogCli(projectId); - let dynamicAwsHostCatalogId = await createDynamicAwsHostCatalogCli( + orgId = await boundaryCli.createOrg(); + let projectId = await boundaryCli.createProject(orgId); + let workerId = await boundaryCli.createControllerLedWorker(); + let authMethodId = await boundaryCli.createPasswordAuthMethod(orgId); + await boundaryCli.makeAuthMethodPrimary(orgId, authMethodId); + let passwordAccountId = + await boundaryCli.createPasswordAccount(authMethodId); + let projectScopeRoleId = await boundaryCli.createRole(projectId); + let orgScopeRoleId = await boundaryCli.createRole(orgId); + let globalScopeRoleId = await boundaryCli.createRole('global'); + let groupId = await boundaryCli.createGroup(orgId); + let userId = await boundaryCli.createUser(orgId); + let staticHostCatalogId = + await boundaryCli.createStaticHostCatalog(projectId); + let dynamicAwsHostCatalogId = await boundaryCli.createDynamicAwsHostCatalog( projectId, awsRegion, ); - let staticHostId = await createStaticHostCli(staticHostCatalogId); - let staticHostSetId = await createHostSetCli(staticHostCatalogId); + let staticHostId = await boundaryCli.createStaticHost(staticHostCatalogId); + let staticHostSetId = await boundaryCli.createHostSet(staticHostCatalogId); let staticCredentialStoreId = - await createStaticCredentialStoreCli(projectId); - const vaultToken = await getVaultToken( + await boundaryCli.createStaticCredentialStore(projectId); + const vaultToken = await vaultCli.getVaultToken( boundaryPolicyName, secretPolicyName, ); - let vaultCredentialStoreId = await createVaultCredentialStoreCli( + let vaultCredentialStoreId = await boundaryCli.createVaultCredentialStore( projectId, vaultAddr, vaultToken, ); let usernamePasswordCredentialId = - await createUsernamePasswordCredentialCli(staticCredentialStoreId); - let tcpTargetId = await createTcpTarget(projectId); + await boundaryCli.createUsernamePasswordCredential( + staticCredentialStoreId, + ); + let tcpTargetId = await boundaryCli.createTcpTarget(projectId); const baseResourcePage = new BaseResourcePage(page); @@ -200,7 +180,7 @@ test('Verify resources can be deleted @ce @aws', async ({ } finally { // Delete org in case the test failed before deleting the org using UI if (orgId && orgDeleted === false) { - await deleteScopeCli(orgId); + await boundaryCli.deleteScope(orgId); } } }); diff --git a/e2e-tests/admin/tests/dynamic-host-catalog-aws-ent.spec.js b/e2e-tests/admin/tests/dynamic-host-catalog-aws-ent.spec.js new file mode 100644 index 0000000000..91ceb1608f --- /dev/null +++ b/e2e-tests/admin/tests/dynamic-host-catalog-aws-ent.spec.js @@ -0,0 +1,236 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { test } from '../../global-setup.js'; +import { expect } from '@playwright/test'; +import { nanoid } from 'nanoid'; + +import * as boundaryCli from '../../helpers/boundary-cli'; +import { CredentialStoresPage } from '../pages/credential-stores.js'; +import { HostCatalogsPage } from '../pages/host-catalogs.js'; +import { OrgsPage } from '../pages/orgs.js'; +import { ProjectsPage } from '../pages/projects.js'; +import { SessionsPage } from '../pages/sessions.js'; +import { TargetsPage } from '../pages/targets.js'; + +test.beforeEach(async ({ page }) => { + await page.goto('/'); +}); + +test.describe('AWS', async () => { + test('Create an AWS Dynamic Host Catalog and set up Host Sets @ent @aws', async ({ + page, + baseURL, + adminAuthMethodId, + adminLoginName, + adminPassword, + awsAccessKeyId, + awsHostSetFilter, + awsHostSetIps, + awsRegion, + awsSecretAccessKey, + targetPort, + sshUser, + sshKeyPath, + }) => { + let orgName; + let connect; + try { + const orgsPage = new OrgsPage(page); + orgName = await orgsPage.createOrg(); + const projectsPage = new ProjectsPage(page); + const projectName = await projectsPage.createProject(); + + // Create host catalog + const hostCatalogName = 'Host Catalog ' + nanoid(); + await page + .getByRole('navigation', { name: 'Resources' }) + .getByRole('link', { name: 'Host Catalogs' }) + .click(); + await page.getByRole('link', { name: 'New', exact: true }).click(); + await page.getByLabel('Name').fill(hostCatalogName); + await page.getByLabel('Description').fill('This is an automated test'); + await page + .getByRole('group', { name: 'Type' }) + .getByLabel('Dynamic') + .click(); + await page + .getByRole('group', { name: 'Provider' }) + .getByLabel('AWS') + .click(); + await page.getByLabel('AWS Region').fill(awsRegion); + await page.getByLabel('Access Key ID').fill(awsAccessKeyId); + await page.getByLabel('Secret Access Key').fill(awsSecretAccessKey); + await page + .getByLabel('Disable credential rotation') + .click({ force: true }); + await page.getByRole('button', { name: 'Save' }).click(); + await expect( + page.getByRole('alert').getByText('Success', { exact: true }), + ).toBeVisible(); + await page.getByRole('button', { name: 'Dismiss' }).click(); + await expect( + page + .getByRole('navigation', { name: 'breadcrumbs' }) + .getByText(hostCatalogName), + ).toBeVisible(); + + // Create first host set + const hostSetName = 'Host Set ' + nanoid(); + await page.getByRole('link', { name: 'Host Sets' }).click(); + await page.getByRole('link', { name: 'New', exact: true }).click(); + await page.getByLabel('Name (Optional)').fill(hostSetName); + await page.getByLabel('Description').fill('This is an automated test'); + await page + .getByRole('group', { name: 'Filter' }) + .getByRole('textbox') + .fill(awsHostSetFilter); + await page + .getByRole('group', { name: 'Filter' }) + .getByRole('button', { name: 'Add' }) + .click(); + await page.getByRole('button', { name: 'Save' }).click(); + await expect( + page.getByRole('alert').getByText('Success', { exact: true }), + ).toBeVisible(); + await page.getByRole('button', { name: 'Dismiss' }).click(); + await expect( + page + .getByRole('navigation', { name: 'breadcrumbs' }) + .getByText(hostSetName), + ).toBeVisible(); + + await page.getByRole('link', { name: 'Hosts' }).click(); + await expect( + page + .getByRole('navigation', { name: 'breadcrumbs' }) + .getByText('Hosts'), + ).toBeVisible(); + + // Check number of hosts in host set + const expectedHosts = JSON.parse(awsHostSetIps); + await expect(async () => { + const rowCount = await page + .getByRole('table') + .getByRole('row') + .filter({ hasNot: page.getByRole('columnheader') }) + .count(); + + if (rowCount !== expectedHosts.length) { + await page.reload(); + await page + .getByRole('navigation', { name: 'breadcrumbs' }) + .getByText(hostSetName) + .waitFor(); + } + expect(rowCount).toBe(expectedHosts.length); + }).toPass(); + + // Navigate to each host in the host set + for (const row of await page + .getByRole('table') + .getByRole('rowgroup') + .nth(1) + .getByRole('row') + .all()) { + const host = row.getByRole('link'); + let hostName = await host.innerText(); + await host.click(); + await expect( + page + .getByRole('navigation', { name: 'breadcrumbs' }) + .getByText(hostName), + ).toBeVisible(); + + await page + .getByRole('navigation', { name: 'breadcrumbs' }) + .getByText(hostSetName) + .click(); + await page.getByRole('link', { name: 'Hosts' }).click(); + } + + // Create a target and add DHC host set as a host source + const targetsPage = new TargetsPage(page); + const targetName = await targetsPage.createSshTargetEnt(targetPort); + await targetsPage.addHostSourceToTarget(hostSetName); + + // Add another host source + const hostCatalogsPage = new HostCatalogsPage(page); + await hostCatalogsPage.createHostCatalog(); + const newHostSetName = await hostCatalogsPage.createHostSet(); + await page + .getByRole('navigation', { name: 'Resources' }) + .getByRole('link', { name: 'Targets' }) + .click(); + await page.getByRole('link', { name: targetName }).click(); + await targetsPage.addHostSourceToTarget(newHostSetName); + + // Remove the host source from the target + await page + .getByRole('link', { name: newHostSetName }) + .locator('..') + .locator('..') + .getByRole('button', { name: 'Manage' }) + .click(); + await page.getByRole('button', { name: 'Remove' }).click(); + await page.getByRole('button', { name: 'OK', exact: true }).click(); + await expect( + page.getByRole('alert').getByText('Success', { exact: true }), + ).toBeVisible(); + await page.getByRole('button', { name: 'Dismiss' }).click(); + + // Set up target credentials + const credentialStoresPage = new CredentialStoresPage(page); + await credentialStoresPage.createStaticCredentialStore(); + const credentialName = + await credentialStoresPage.createStaticCredentialKeyPair( + sshUser, + sshKeyPath, + ); + await targetsPage.addInjectedCredentialsToTarget( + targetName, + credentialName, + ); + + // Connect to target + await boundaryCli.authenticateBoundary( + baseURL, + adminAuthMethodId, + adminLoginName, + adminPassword, + ); + const orgId = await boundaryCli.getOrgIdFromName(orgName); + const projectId = await boundaryCli.getProjectIdFromName( + orgId, + projectName, + ); + const targetId = await boundaryCli.getTargetIdFromName( + projectId, + targetName, + ); + connect = await boundaryCli.connectSshToTarget(targetId); + const sessionsPage = new SessionsPage(page); + await sessionsPage.waitForSessionToBeVisible(targetName); + } finally { + if (connect) { + connect.kill('SIGTERM'); + } + + await boundaryCli.authenticateBoundary( + baseURL, + adminAuthMethodId, + adminLoginName, + adminPassword, + ); + + if (orgName) { + const orgId = await boundaryCli.getOrgIdFromName(orgName); + if (orgId) { + await boundaryCli.deleteScope(orgId); + } + } + } + }); +}); diff --git a/e2e-tests/admin/tests/dynamic-host-catalog-aws.spec.js b/e2e-tests/admin/tests/dynamic-host-catalog-aws.spec.js index 451e8576f3..bce71d3426 100644 --- a/e2e-tests/admin/tests/dynamic-host-catalog-aws.spec.js +++ b/e2e-tests/admin/tests/dynamic-host-catalog-aws.spec.js @@ -3,29 +3,24 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { test } from '../playwright.config.js'; +import { test } from '../../global-setup.js'; import { expect } from '@playwright/test'; import { nanoid } from 'nanoid'; -import { authenticatedState } from '../global-setup.js'; -import { - authenticateBoundaryCli, - deleteScopeCli, - getOrgIdFromNameCli, -} from '../../helpers/boundary-cli.js'; +import * as boundaryCli from '../../helpers/boundary-cli'; +import { CredentialStoresPage } from '../pages/credential-stores.js'; import { HostCatalogsPage } from '../pages/host-catalogs.js'; import { OrgsPage } from '../pages/orgs.js'; import { ProjectsPage } from '../pages/projects.js'; +import { SessionsPage } from '../pages/sessions.js'; import { TargetsPage } from '../pages/targets.js'; -test.use({ storageState: authenticatedState }); - test.beforeEach(async ({ page }) => { await page.goto('/'); }); test.describe('AWS', async () => { - test('Create an AWS Dynamic Host Catalog and set up Host Sets @ce @ent @aws', async ({ + test('Create an AWS Dynamic Host Catalog and set up Host Sets @ce @aws', async ({ page, baseURL, adminAuthMethodId, @@ -37,13 +32,16 @@ test.describe('AWS', async () => { awsRegion, awsSecretAccessKey, targetPort, + sshUser, + sshKeyPath, }) => { let orgName; + let connect; try { const orgsPage = new OrgsPage(page); orgName = await orgsPage.createOrg(); const projectsPage = new ProjectsPage(page); - await projectsPage.createProject(); + const projectName = await projectsPage.createProject(); // Create host catalog const hostCatalogName = 'Host Catalog ' + nanoid(); @@ -112,37 +110,32 @@ test.describe('AWS', async () => { ).toBeVisible(); // Check number of hosts in host set - let i = 0; - let rowCount = 0; - let expectedHosts = JSON.parse(awsHostSetIps); - do { - i = i + 1; - // Getting the number of rows in the second rowgroup (the first rowgroup is the header row) - rowCount = await page + const expectedHosts = JSON.parse(awsHostSetIps); + await expect(async () => { + const rowCount = await page .getByRole('table') - .getByRole('rowgroup') - .nth(1) .getByRole('row') + .filter({ hasNot: page.getByRole('columnheader') }) .count(); - await page.reload(); - await page - .getByRole('navigation', { name: 'breadcrumbs' }) - .getByText(hostSetName) - .waitFor(); - } while (i < 5); - expect(rowCount).toBe(expectedHosts.length); + if (rowCount !== expectedHosts.length) { + await page.reload(); + await page + .getByRole('navigation', { name: 'breadcrumbs' }) + .getByText(hostSetName) + .waitFor(); + } + expect(rowCount).toBe(expectedHosts.length); + }).toPass(); // Navigate to each host in the host set - for (let i = 0; i < expectedHosts.length; i++) { - const host = await page - .getByRole('table') - .getByRole('rowgroup') - .nth(1) - .getByRole('row') - .nth(i) - .getByRole('link'); - + for (const row of await page + .getByRole('table') + .getByRole('rowgroup') + .nth(1) + .getByRole('row') + .all()) { + const host = row.getByRole('link'); let hostName = await host.innerText(); await host.click(); await expect( @@ -188,10 +181,44 @@ test.describe('AWS', async () => { ).toBeVisible(); await page.getByRole('button', { name: 'Dismiss' }).click(); - // Add the host source back - await targetsPage.addHostSourceToTarget(newHostSetName); + // Set up target credentials + const credentialStoresPage = new CredentialStoresPage(page); + await credentialStoresPage.createStaticCredentialStore(); + const credentialName = + await credentialStoresPage.createStaticCredentialKeyPair( + sshUser, + sshKeyPath, + ); + await targetsPage.addBrokeredCredentialsToTarget( + targetName, + credentialName, + ); + + // Connect to target + await boundaryCli.authenticateBoundary( + baseURL, + adminAuthMethodId, + adminLoginName, + adminPassword, + ); + const orgId = await boundaryCli.getOrgIdFromName(orgName); + const projectId = await boundaryCli.getProjectIdFromName( + orgId, + projectName, + ); + const targetId = await boundaryCli.getTargetIdFromName( + projectId, + targetName, + ); + connect = await boundaryCli.connectSshToTarget(targetId); + const sessionsPage = new SessionsPage(page); + await sessionsPage.waitForSessionToBeVisible(targetName); } finally { - await authenticateBoundaryCli( + if (connect) { + connect.kill('SIGTERM'); + } + + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, @@ -199,9 +226,9 @@ test.describe('AWS', async () => { ); if (orgName) { - const orgId = await getOrgIdFromNameCli(orgName); + const orgId = await boundaryCli.getOrgIdFromName(orgName); if (orgId) { - await deleteScopeCli(orgId); + await boundaryCli.deleteScope(orgId); } } } diff --git a/e2e-tests/admin/tests/fixtures/auth-policy.hcl b/e2e-tests/admin/tests/fixtures/auth-policy.hcl new file mode 100644 index 0000000000..75fa6743e0 --- /dev/null +++ b/e2e-tests/admin/tests/fixtures/auth-policy.hcl @@ -0,0 +1,6 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: BUSL-1.1 + +path "identity/oidc/provider/my-provider/authorize" { + capabilities = [ "read" ] +} diff --git a/e2e-tests/admin/tests/global-settings.spec.js b/e2e-tests/admin/tests/global-settings.spec.js index 6b165ce6bf..670e3a1cc2 100644 --- a/e2e-tests/admin/tests/global-settings.spec.js +++ b/e2e-tests/admin/tests/global-settings.spec.js @@ -3,20 +3,13 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { test } from '../playwright.config.js'; +import { test } from '../../global-setup.js'; import { expect } from '@playwright/test'; -import { authenticatedState } from '../global-setup.js'; -import { - authenticateBoundaryCli, - deletePolicyCli, - getPolicyIdFromNameCli, -} from '../../helpers/boundary-cli.js'; +import * as boundaryCli from '../../helpers/boundary-cli'; import { OrgsPage } from '../pages/orgs.js'; import { StoragePoliciesPage } from '../pages/storage-policies.js'; -test.use({ storageState: authenticatedState }); - test('Global Settings @ent @aws @docker', async ({ page, baseURL, @@ -52,17 +45,17 @@ test('Global Settings @ent @aws @docker', async ({ await expect(page.getByRole('heading', { name: 'Orgs' })).toBeVisible(); } finally { if (policyName) { - await authenticateBoundaryCli( + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, adminPassword, ); - const storagePolicyId = await getPolicyIdFromNameCli( + const storagePolicyId = await boundaryCli.getPolicyIdFromName( 'global', policyName, ); - await deletePolicyCli(storagePolicyId); + await boundaryCli.deletePolicy(storagePolicyId); } } }); diff --git a/e2e-tests/admin/tests/group-role.spec.js b/e2e-tests/admin/tests/group-role.spec.js index 96473c5d6c..f135dcb644 100644 --- a/e2e-tests/admin/tests/group-role.spec.js +++ b/e2e-tests/admin/tests/group-role.spec.js @@ -3,23 +3,15 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { test } from '../playwright.config.js'; +import { test } from '../../global-setup.js'; -import { authenticatedState } from '../global-setup.js'; -import { - authenticateBoundaryCli, - checkBoundaryCli, - deleteScopeCli, - getOrgIdFromNameCli, -} from '../../helpers/boundary-cli.js'; +import * as boundaryCli from '../../helpers/boundary-cli'; import { GroupsPage } from '../pages/groups.js'; import { OrgsPage } from '../pages/orgs.js'; import { RolesPage } from '../pages/roles.js'; -test.use({ storageState: authenticatedState }); - test.beforeAll(async () => { - await checkBoundaryCli(); + await boundaryCli.checkBoundaryCli(); }); test('Verify a new role can be created and associated with a group @ce @ent @aws @docker', async ({ @@ -42,15 +34,15 @@ test('Verify a new role can be created and associated with a group @ce @ent @aws await rolesPage.addPrincipalToRole(groupName); await rolesPage.addGrantsToRole('ids=*;type=*;actions=read,list'); } finally { - await authenticateBoundaryCli( + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, adminPassword, ); - const orgId = await getOrgIdFromNameCli(orgName); + const orgId = await boundaryCli.getOrgIdFromName(orgName); if (orgId) { - await deleteScopeCli(orgId); + await boundaryCli.deleteScope(orgId); } } }); diff --git a/e2e-tests/admin/tests/login.spec.js b/e2e-tests/admin/tests/login.spec.js index fa3f9c0f8f..16dd760ed7 100644 --- a/e2e-tests/admin/tests/login.spec.js +++ b/e2e-tests/admin/tests/login.spec.js @@ -3,11 +3,14 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { test } from '../playwright.config.js'; +import { test } from '../../global-setup.js'; import { expect } from '@playwright/test'; import { LoginPage } from '../pages/login.js'; +// Reset storage state for this file to avoid being authenticated +test.use({ storageState: { cookies: [], origins: [] } }); + test('Log in, log out, and then log back in @ce @ent @aws @docker', async ({ page, adminLoginName, diff --git a/e2e-tests/admin/tests/pagination.spec.js b/e2e-tests/admin/tests/pagination.spec.js index c109541f44..4fa270f036 100644 --- a/e2e-tests/admin/tests/pagination.spec.js +++ b/e2e-tests/admin/tests/pagination.spec.js @@ -3,17 +3,10 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { test } from '../playwright.config.js'; +import { test } from '../../global-setup.js'; import { expect } from '@playwright/test'; -import { authenticatedState } from '../global-setup.js'; -import { - createOrgHttp, - createProjectHttp, - createTargetHttp, -} from '../../helpers/boundary-http.js'; - -test.use({ storageState: authenticatedState }); +import * as boundaryHttp from '../../helpers/boundary-http.js'; test('Search and Pagination (Targets) @ce @ent @aws @docker', async ({ page, @@ -21,14 +14,18 @@ test('Search and Pagination (Targets) @ce @ent @aws @docker', async ({ }) => { let org; try { - org = await createOrgHttp(request); - const project = await createProjectHttp(request, org.id); + org = await boundaryHttp.createOrg(request); + const project = await boundaryHttp.createProject(request, org.id); // Create targets let targets = []; const targetCount = 15; for (let i = 0; i < targetCount; i++) { - const target = await createTargetHttp(request, project.id, 'tcp', 22); + const target = await boundaryHttp.createTarget(request, { + scopeId: project.id, + type: 'tcp', + port: 22, + }); targets.push(target); } diff --git a/e2e-tests/admin/tests/session-recording-aws-ent.spec.js b/e2e-tests/admin/tests/session-recording-aws-ent.spec.js index 2fdc0c0201..1119cc326c 100644 --- a/e2e-tests/admin/tests/session-recording-aws-ent.spec.js +++ b/e2e-tests/admin/tests/session-recording-aws-ent.spec.js @@ -3,24 +3,11 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { test } from '../playwright.config.js'; +import { test } from '../../global-setup.js'; import { expect } from '@playwright/test'; import { execSync } from 'child_process'; -import { authenticatedState } from '../global-setup.js'; -import { - authenticateBoundaryCli, - checkBoundaryCli, - connectSshToTarget, - deleteScopeCli, - waitForSessionRecordingCli, - deleteStorageBucketCli, - deletePolicyCli, - getOrgIdFromNameCli, - getPolicyIdFromNameCli, - getProjectIdFromNameCli, - getTargetIdFromNameCli, -} from '../../helpers/boundary-cli.js'; +import * as boundaryCli from '../../helpers/boundary-cli'; import { CredentialStoresPage } from '../pages/credential-stores.js'; import { OrgsPage } from '../pages/orgs.js'; import { ProjectsPage } from '../pages/projects.js'; @@ -30,10 +17,8 @@ import { StorageBucketsPage } from '../pages/storage-buckets.js'; import { StoragePoliciesPage } from '../pages/storage-policies.js'; import { TargetsPage } from '../pages/targets.js'; -test.use({ storageState: authenticatedState }); - test.beforeAll(async () => { - await checkBoundaryCli(); + await boundaryCli.checkBoundaryCli(); }); test('Session Recording Test (AWS) @ent @aws', async ({ @@ -58,7 +43,7 @@ test('Session Recording Test (AWS) @ent @aws', async ({ let storageBucket; let connect; try { - await authenticateBoundaryCli( + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, @@ -130,10 +115,16 @@ test('Session Recording Test (AWS) @ent @aws', async ({ await orgsPage.attachStoragePolicy(policyName); // Establish connection to target and cancel it - orgId = await getOrgIdFromNameCli(orgName); - const projectId = await getProjectIdFromNameCli(orgId, projectName); - const targetId = await getTargetIdFromNameCli(projectId, targetName); - connect = await connectSshToTarget(targetId); + orgId = await boundaryCli.getOrgIdFromName(orgName); + const projectId = await boundaryCli.getProjectIdFromName( + orgId, + projectName, + ); + const targetId = await boundaryCli.getTargetIdFromName( + projectId, + targetName, + ); + connect = await boundaryCli.connectSshToTarget(targetId); await page.getByRole('link', { name: 'Projects', exact: true }).click(); await page.getByRole('link', { name: projectName }).click(); const sessionsPage = new SessionsPage(page); @@ -147,7 +138,7 @@ test('Session Recording Test (AWS) @ent @aws', async ({ page.getByRole('alert').getByText('Success', { exact: true }), ).toBeVisible(); await page.getByRole('button', { name: 'Dismiss', exact: true }).click(); - await waitForSessionRecordingCli(storageBucket.id); + await boundaryCli.waitForSessionRecording(storageBucket.id); // Play back session recording await page.getByRole('link', { name: 'Orgs', exact: true }).click(); @@ -234,19 +225,23 @@ test('Session Recording Test (AWS) @ent @aws', async ({ await page.getByRole('link', { name: targetName }).click(); await targetsPage.detachStorageBucket(); } finally { + // End `boundary connect` process + if (connect) { + connect.kill('SIGTERM'); + } + if (policyName) { - const storagePolicyId = await getPolicyIdFromNameCli(orgId, policyName); - await deletePolicyCli(storagePolicyId); + const storagePolicyId = await boundaryCli.getPolicyIdFromName( + orgId, + policyName, + ); + await boundaryCli.deletePolicy(storagePolicyId); } if (storageBucket) { - await deleteStorageBucketCli(storageBucket.id); + await boundaryCli.deleteStorageBucket(storageBucket.id); } if (orgId) { - await deleteScopeCli(orgId); - } - // End `boundary connect` process - if (connect) { - connect.kill('SIGTERM'); + await boundaryCli.deleteScope(orgId); } } }); diff --git a/e2e-tests/admin/tests/session-recording-minio-ent.spec.js b/e2e-tests/admin/tests/session-recording-minio-ent.spec.js index 591a16ea86..4fc636113c 100644 --- a/e2e-tests/admin/tests/session-recording-minio-ent.spec.js +++ b/e2e-tests/admin/tests/session-recording-minio-ent.spec.js @@ -3,24 +3,11 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { test } from '../playwright.config.js'; +import { test } from '../../global-setup.js'; import { expect } from '@playwright/test'; import { execSync } from 'child_process'; -import { authenticatedState } from '../global-setup.js'; -import { - authenticateBoundaryCli, - checkBoundaryCli, - connectSshToTarget, - deleteScopeCli, - waitForSessionRecordingCli, - deleteStorageBucketCli, - deletePolicyCli, - getOrgIdFromNameCli, - getPolicyIdFromNameCli, - getProjectIdFromNameCli, - getTargetIdFromNameCli, -} from '../../helpers/boundary-cli.js'; +import * as boundaryCli from '../../helpers/boundary-cli'; import { CredentialStoresPage } from '../pages/credential-stores.js'; import { OrgsPage } from '../pages/orgs.js'; import { ProjectsPage } from '../pages/projects.js'; @@ -30,10 +17,8 @@ import { StorageBucketsPage } from '../pages/storage-buckets.js'; import { StoragePoliciesPage } from '../pages/storage-policies.js'; import { TargetsPage } from '../pages/targets.js'; -test.use({ storageState: authenticatedState }); - test.beforeAll(async () => { - await checkBoundaryCli(); + await boundaryCli.checkBoundaryCli(); }); test('Session Recording Test (MinIO) @ent @docker', async ({ @@ -60,7 +45,7 @@ test('Session Recording Test (MinIO) @ent @docker', async ({ let storageBucket; let connect; try { - await authenticateBoundaryCli( + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, @@ -137,10 +122,16 @@ test('Session Recording Test (MinIO) @ent @docker', async ({ await orgsPage.attachStoragePolicy(policyName); // Establish connection to target and cancel it - orgId = await getOrgIdFromNameCli(orgName); - const projectId = await getProjectIdFromNameCli(orgId, projectName); - const targetId = await getTargetIdFromNameCli(projectId, targetName); - connect = await connectSshToTarget(targetId); + orgId = await boundaryCli.getOrgIdFromName(orgName); + const projectId = await boundaryCli.getProjectIdFromName( + orgId, + projectName, + ); + const targetId = await boundaryCli.getTargetIdFromName( + projectId, + targetName, + ); + connect = await boundaryCli.connectSshToTarget(targetId); await page.getByRole('link', { name: 'Projects', exact: true }).click(); await page.getByRole('link', { name: projectName }).click(); const sessionsPage = new SessionsPage(page); @@ -154,7 +145,7 @@ test('Session Recording Test (MinIO) @ent @docker', async ({ page.getByRole('alert').getByText('Success', { exact: true }), ).toBeVisible(); await page.getByRole('button', { name: 'Dismiss', exact: true }).click(); - await waitForSessionRecordingCli(storageBucket.id); + await boundaryCli.waitForSessionRecording(storageBucket.id); // Play back session recording await page.getByRole('link', { name: 'Orgs', exact: true }).click(); @@ -241,19 +232,23 @@ test('Session Recording Test (MinIO) @ent @docker', async ({ await page.getByRole('link', { name: targetName }).click(); await targetsPage.detachStorageBucket(); } finally { + // End `boundary connect` process + if (connect) { + connect.kill('SIGTERM'); + } + if (policyName) { - const storagePolicyId = await getPolicyIdFromNameCli(orgId, policyName); - await deletePolicyCli(storagePolicyId); + const storagePolicyId = await boundaryCli.getPolicyIdFromName( + orgId, + policyName, + ); + await boundaryCli.deletePolicy(storagePolicyId); } if (storageBucket) { - await deleteStorageBucketCli(storageBucket.id); + await boundaryCli.deleteStorageBucket(storageBucket.id); } if (orgId) { - await deleteScopeCli(orgId); - } - // End `boundary connect` process - if (connect) { - connect.kill('SIGTERM'); + await boundaryCli.deleteScope(orgId); } } }); diff --git a/e2e-tests/admin/tests/ssh-certificate-injection-vault-ent.spec.js b/e2e-tests/admin/tests/ssh-certificate-injection-vault-ent.spec.js index 0a71fa1f66..45b77a253f 100644 --- a/e2e-tests/admin/tests/ssh-certificate-injection-vault-ent.spec.js +++ b/e2e-tests/admin/tests/ssh-certificate-injection-vault-ent.spec.js @@ -3,20 +3,11 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { test } from '../playwright.config.js'; +import { test } from '../../global-setup.js'; import { execSync } from 'child_process'; -import { authenticatedState } from '../global-setup.js'; -import { - authenticateBoundaryCli, - checkBoundaryCli, - connectSshToTarget, - deleteScopeCli, - getOrgIdFromNameCli, - getProjectIdFromNameCli, - getTargetIdFromNameCli, -} from '../../helpers/boundary-cli.js'; -import { checkVaultCli } from '../../helpers/vault-cli.js'; +import * as boundaryCli from '../../helpers/boundary-cli'; +import * as vaultCli from '../../helpers/vault-cli'; import { CredentialStoresPage } from '../pages/credential-stores.js'; import { OrgsPage } from '../pages/orgs.js'; import { ProjectsPage } from '../pages/projects.js'; @@ -30,11 +21,9 @@ const secretsPath = 'e2e_secrets'; // This must match the secret name in ssh-policy.hcl const secretName = 'boundary-client'; -test.use({ storageState: authenticatedState }); - test.beforeAll(async () => { - await checkBoundaryCli(); - await checkVaultCli(); + await boundaryCli.checkBoundaryCli(); + await vaultCli.checkVaultCli(); }); test.beforeEach(async () => { @@ -70,8 +59,8 @@ test('SSH Certificate Injection @ent @docker', async ({ `vault write ${secretsPath}/roles/${secretName} @./admin/tests/fixtures/ssh-certificate-injection-role.json`, ); - const private_key = atob(sshCaKey); - const public_key = atob(sshCaKeyPublic); + const private_key = Buffer.from(sshCaKey, 'base64'); + const public_key = Buffer.from(sshCaKeyPublic, 'base64'); execSync( `vault write ${secretsPath}/config/ca` + @@ -127,32 +116,34 @@ test('SSH Certificate Injection @ent @docker', async ({ ); // Connect to target - await authenticateBoundaryCli( + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, adminPassword, ); - orgId = await getOrgIdFromNameCli(orgName); - const projectId = await getProjectIdFromNameCli(orgId, projectName); - const targetId = await getTargetIdFromNameCli(projectId, targetName); - connect = await connectSshToTarget(targetId); + orgId = await boundaryCli.getOrgIdFromName(orgName); + const projectId = await boundaryCli.getProjectIdFromName( + orgId, + projectName, + ); + const targetId = await boundaryCli.getTargetIdFromName( + projectId, + targetName, + ); + connect = await boundaryCli.connectSshToTarget(targetId); const sessionsPage = new SessionsPage(page); await sessionsPage.waitForSessionToBeVisible(targetName); - await page - .getByRole('cell', { name: targetName }) - .locator('..') - .getByRole('button', { name: 'Cancel' }) - .click(); } finally { - if (orgId) { - await deleteScopeCli(orgId); - } // End `boundary connect` process if (connect) { connect.kill('SIGTERM'); } + if (orgId) { + await boundaryCli.deleteScope(orgId); + } + execSync(`vault secrets disable ${secretsPath}`); execSync(`vault policy delete ${sshPolicyName}`); execSync(`vault policy delete ${boundaryPolicyName}`); diff --git a/e2e-tests/admin/tests/ssh-credential-injection-vault-ent.spec.js b/e2e-tests/admin/tests/ssh-credential-injection-vault-ent.spec.js index 23ff54cf8d..34023017cd 100644 --- a/e2e-tests/admin/tests/ssh-credential-injection-vault-ent.spec.js +++ b/e2e-tests/admin/tests/ssh-credential-injection-vault-ent.spec.js @@ -3,20 +3,11 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { test } from '../playwright.config.js'; +import { test } from '../../global-setup.js'; import { execSync } from 'child_process'; -import { authenticatedState } from '../global-setup.js'; -import { - authenticateBoundaryCli, - checkBoundaryCli, - connectSshToTarget, - deleteScopeCli, - getOrgIdFromNameCli, - getProjectIdFromNameCli, - getTargetIdFromNameCli, -} from '../../helpers/boundary-cli.js'; -import { checkVaultCli } from '../../helpers/vault-cli.js'; +import * as boundaryCli from '../../helpers/boundary-cli'; +import * as vaultCli from '../../helpers/vault-cli'; import { CredentialStoresPage } from '../pages/credential-stores.js'; import { OrgsPage } from '../pages/orgs.js'; import { ProjectsPage } from '../pages/projects.js'; @@ -28,11 +19,9 @@ const secretName = 'cred'; const secretPolicyName = 'kv-policy'; const boundaryPolicyName = 'boundary-controller'; -test.use({ storageState: authenticatedState }); - test.beforeAll(async () => { - await checkBoundaryCli(); - await checkVaultCli(); + await boundaryCli.checkBoundaryCli(); + await vaultCli.checkVaultCli(); }); test.beforeEach(async ({ page }) => { @@ -117,30 +106,32 @@ test('SSH Credential Injection (Vault User & Key Pair) @ent @docker', async ({ ); // Connect to target - await authenticateBoundaryCli( + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, adminPassword, ); - orgId = await getOrgIdFromNameCli(orgName); - const projectId = await getProjectIdFromNameCli(orgId, projectName); - const targetId = await getTargetIdFromNameCli(projectId, targetName); - connect = await connectSshToTarget(targetId); + orgId = await boundaryCli.getOrgIdFromName(orgName); + const projectId = await boundaryCli.getProjectIdFromName( + orgId, + projectName, + ); + const targetId = await boundaryCli.getTargetIdFromName( + projectId, + targetName, + ); + connect = await boundaryCli.connectSshToTarget(targetId); const sessionsPage = new SessionsPage(page); await sessionsPage.waitForSessionToBeVisible(targetName); - await page - .getByRole('cell', { name: targetName }) - .locator('..') - .getByRole('button', { name: 'Cancel' }) - .click(); } finally { - if (orgId) { - await deleteScopeCli(orgId); - } // End `boundary connect` process if (connect) { connect.kill('SIGTERM'); } + + if (orgId) { + await boundaryCli.deleteScope(orgId); + } } }); diff --git a/e2e-tests/admin/tests/target-ent.spec.js b/e2e-tests/admin/tests/target-ent.spec.js index 9590077a26..d9e6f177f8 100644 --- a/e2e-tests/admin/tests/target-ent.spec.js +++ b/e2e-tests/admin/tests/target-ent.spec.js @@ -3,20 +3,10 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { test } from '../playwright.config.js'; +import { test } from '../../global-setup.js'; import { expect } from '@playwright/test'; -import { authenticatedState } from '../global-setup.js'; -import { - authenticateBoundaryCli, - checkBoundaryCli, - connectToTarget, - connectSshToTarget, - deleteScopeCli, - getOrgIdFromNameCli, - getProjectIdFromNameCli, - getTargetIdFromNameCli, -} from '../../helpers/boundary-cli.js'; +import * as boundaryCli from '../../helpers/boundary-cli'; import { CredentialStoresPage } from '../pages/credential-stores.js'; import { HostCatalogsPage } from '../pages/host-catalogs.js'; import { OrgsPage } from '../pages/orgs.js'; @@ -24,10 +14,8 @@ import { ProjectsPage } from '../pages/projects.js'; import { SessionsPage } from '../pages/sessions.js'; import { TargetsPage } from '../pages/targets.js'; -test.use({ storageState: authenticatedState }); - test.beforeAll(async () => { - await checkBoundaryCli(); + await boundaryCli.checkBoundaryCli(); }); test('Verify session created for TCP target @ent @aws @docker', async ({ @@ -55,16 +43,22 @@ test('Verify session created for TCP target @ent @aws @docker', async ({ targetPort, ); - await authenticateBoundaryCli( + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, adminPassword, ); - orgId = await getOrgIdFromNameCli(orgName); - const projectId = await getProjectIdFromNameCli(orgId, projectName); - const targetId = await getTargetIdFromNameCli(projectId, targetName); - connect = await connectToTarget(targetId, sshUser, sshKeyPath); + orgId = await boundaryCli.getOrgIdFromName(orgName); + const projectId = await boundaryCli.getProjectIdFromName( + orgId, + projectName, + ); + const targetId = await boundaryCli.getTargetIdFromName( + projectId, + targetName, + ); + connect = await boundaryCli.connectToTarget(targetId, sshUser, sshKeyPath); const sessionsPage = new SessionsPage(page); await sessionsPage.waitForSessionToBeVisible(targetName); await page @@ -74,7 +68,7 @@ test('Verify session created for TCP target @ent @aws @docker', async ({ .click(); } finally { if (orgId) { - await deleteScopeCli(orgId); + await boundaryCli.deleteScope(orgId); } // End `boundary connect` process if (connect) { @@ -119,16 +113,22 @@ test('Verify session created for SSH target @ent @aws @docker', async ({ credentialName, ); - await authenticateBoundaryCli( + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, adminPassword, ); - orgId = await getOrgIdFromNameCli(orgName); - const projectId = await getProjectIdFromNameCli(orgId, projectName); - const targetId = await getTargetIdFromNameCli(projectId, targetName); - connect = await connectSshToTarget(targetId); + orgId = await boundaryCli.getOrgIdFromName(orgName); + const projectId = await boundaryCli.getProjectIdFromName( + orgId, + projectName, + ); + const targetId = await boundaryCli.getTargetIdFromName( + projectId, + targetName, + ); + connect = await boundaryCli.connectSshToTarget(targetId); const sessionsPage = new SessionsPage(page); await sessionsPage.waitForSessionToBeVisible(targetName); await page @@ -138,7 +138,7 @@ test('Verify session created for SSH target @ent @aws @docker', async ({ .click(); } finally { if (orgId) { - await deleteScopeCli(orgId); + await boundaryCli.deleteScope(orgId); } // End `boundary connect` process if (connect) { @@ -215,16 +215,22 @@ test('SSH target with host sources @ent @aws @docker', async ({ ); // Connect to target - await authenticateBoundaryCli( + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, adminPassword, ); - orgId = await getOrgIdFromNameCli(orgName); - const projectId = await getProjectIdFromNameCli(orgId, projectName); - const targetId = await getTargetIdFromNameCli(projectId, targetName); - connect = await connectSshToTarget(targetId); + orgId = await boundaryCli.getOrgIdFromName(orgName); + const projectId = await boundaryCli.getProjectIdFromName( + orgId, + projectName, + ); + const targetId = await boundaryCli.getTargetIdFromName( + projectId, + targetName, + ); + connect = await boundaryCli.connectSshToTarget(targetId); const sessionsPage = new SessionsPage(page); await sessionsPage.waitForSessionToBeVisible(targetName); await page @@ -234,7 +240,7 @@ test('SSH target with host sources @ent @aws @docker', async ({ .click(); } finally { if (orgId) { - await deleteScopeCli(orgId); + await boundaryCli.deleteScope(orgId); } // End `boundary connect` process if (connect) { diff --git a/e2e-tests/admin/tests/target.spec.js b/e2e-tests/admin/tests/target.spec.js index 5c9b1b710f..e6f59c89dd 100644 --- a/e2e-tests/admin/tests/target.spec.js +++ b/e2e-tests/admin/tests/target.spec.js @@ -3,19 +3,10 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { test } from '../playwright.config.js'; +import { test } from '../../global-setup.js'; import { expect } from '@playwright/test'; -import { authenticatedState } from '../global-setup.js'; -import { - authenticateBoundaryCli, - checkBoundaryCli, - connectToTarget, - deleteScopeCli, - getOrgIdFromNameCli, - getProjectIdFromNameCli, - getTargetIdFromNameCli, -} from '../../helpers/boundary-cli.js'; +import * as boundaryCli from '../../helpers/boundary-cli'; import { BasePage } from '../pages/base.js'; import { HostCatalogsPage } from '../pages/host-catalogs.js'; import { OrgsPage } from '../pages/orgs.js'; @@ -23,10 +14,8 @@ import { ProjectsPage } from '../pages/projects.js'; import { SessionsPage } from '../pages/sessions.js'; import { TargetsPage } from '../pages/targets.js'; -test.use({ storageState: authenticatedState }); - test.beforeAll(async () => { - await checkBoundaryCli(); + await boundaryCli.checkBoundaryCli(); }); test('Verify session created to target with host, then cancel the session @ce @aws @docker', async ({ @@ -73,16 +62,22 @@ test('Verify session created to target with host, then cancel the session @ce @a await targetsPage.removeHostSourceFromTarget(hostSetName2); // Connect to target - await authenticateBoundaryCli( + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, adminPassword, ); - orgId = await getOrgIdFromNameCli(orgName); - const projectId = await getProjectIdFromNameCli(orgId, projectName); - const targetId = await getTargetIdFromNameCli(projectId, targetName); - connect = await connectToTarget(targetId, sshUser, sshKeyPath); + orgId = await boundaryCli.getOrgIdFromName(orgName); + const projectId = await boundaryCli.getProjectIdFromName( + orgId, + projectName, + ); + const targetId = await boundaryCli.getTargetIdFromName( + projectId, + targetName, + ); + connect = await boundaryCli.connectToTarget(targetId, sshUser, sshKeyPath); const sessionsPage = new SessionsPage(page); await sessionsPage.waitForSessionToBeVisible(targetName); await page @@ -92,7 +87,7 @@ test('Verify session created to target with host, then cancel the session @ce @a .click(); } finally { if (orgId) { - await deleteScopeCli(orgId); + await boundaryCli.deleteScope(orgId); } // End `boundary connect` process if (connect) { @@ -126,16 +121,22 @@ test('Verify session created to target with address, then cancel the session @ce targetPort, ); - await authenticateBoundaryCli( + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, adminPassword, ); - orgId = await getOrgIdFromNameCli(orgName); - const projectId = await getProjectIdFromNameCli(orgId, projectName); - const targetId = await getTargetIdFromNameCli(projectId, targetName); - connect = await connectToTarget(targetId, sshUser, sshKeyPath); + orgId = await boundaryCli.getOrgIdFromName(orgName); + const projectId = await boundaryCli.getProjectIdFromName( + orgId, + projectName, + ); + const targetId = await boundaryCli.getTargetIdFromName( + projectId, + targetName, + ); + connect = await boundaryCli.connectToTarget(targetId, sshUser, sshKeyPath); const sessionsPage = new SessionsPage(page); await sessionsPage.waitForSessionToBeVisible(targetName); await page @@ -145,7 +146,7 @@ test('Verify session created to target with address, then cancel the session @ce .click(); } finally { if (orgId) { - await deleteScopeCli(orgId); + await boundaryCli.deleteScope(orgId); } // End `boundary connect` process if (connect) { @@ -218,15 +219,15 @@ test('Verify TCP target is updated @ce @aws @docker', async ({ const basePage = new BasePage(page); await basePage.dismissSuccessAlert(); } finally { - await authenticateBoundaryCli( + await boundaryCli.authenticateBoundary( baseURL, adminAuthMethodId, adminLoginName, adminPassword, ); - const orgId = await getOrgIdFromNameCli(orgName); + const orgId = await boundaryCli.getOrgIdFromName(orgName); if (orgId) { - await deleteScopeCli(orgId); + await boundaryCli.deleteScope(orgId); } } }); diff --git a/e2e-tests/admin/tests/worker-ent.spec.js b/e2e-tests/admin/tests/worker-ent.spec.js index 492472c807..9faec160b3 100644 --- a/e2e-tests/admin/tests/worker-ent.spec.js +++ b/e2e-tests/admin/tests/worker-ent.spec.js @@ -3,13 +3,9 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { test } from '../playwright.config.js'; +import { test } from '../../global-setup.js'; import { expect } from '@playwright/test'; -import { authenticatedState } from '../global-setup.js'; - -test.use({ storageState: authenticatedState }); - test('Create a worker (enterprise) @ent @docker @aws', async ({ page, browserName, diff --git a/e2e-tests/admin/tests/worker-tags.spec.js b/e2e-tests/admin/tests/worker-tags.spec.js index 2f6712c64f..414e622719 100644 --- a/e2e-tests/admin/tests/worker-tags.spec.js +++ b/e2e-tests/admin/tests/worker-tags.spec.js @@ -3,15 +3,12 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { test } from '../playwright.config.js'; +import { test } from '../../global-setup.js'; import { expect } from '@playwright/test'; import { customAlphabet } from 'nanoid'; -import { authenticatedState } from '../global-setup.js'; import { WorkersPage } from '../pages/workers.js'; -test.use({ storageState: authenticatedState }); - test('Worker Tags @ce @ent @aws @docker', async ({ page }) => { const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 10); diff --git a/e2e-tests/admin/tests/worker.spec.js b/e2e-tests/admin/tests/worker.spec.js index 906a49316c..05601cf625 100644 --- a/e2e-tests/admin/tests/worker.spec.js +++ b/e2e-tests/admin/tests/worker.spec.js @@ -3,13 +3,9 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { test } from '../playwright.config.js'; +import { test } from '../../global-setup.js'; import { expect } from '@playwright/test'; -import { authenticatedState } from '../global-setup.js'; - -test.use({ storageState: authenticatedState }); - test('Create a worker @ce @docker @aws', async ({ page, browserName }) => { test.skip(browserName === 'webkit', 'Bug in worker form on Safari'); diff --git a/e2e-tests/desktop/fixtures/baseTest.js b/e2e-tests/desktop/fixtures/baseTest.js index b101064472..0b15e611d8 100644 --- a/e2e-tests/desktop/fixtures/baseTest.js +++ b/e2e-tests/desktop/fixtures/baseTest.js @@ -6,8 +6,8 @@ import { mergeTests } from '@playwright/test'; import { electronTest } from './electronTest.js'; import { authenticateTest } from './authenticateTest.js'; +import { test as envTest } from '../../global-setup.js'; -// Redundant since authenticateTest already extends electronTest but this is for demonstration purposes // The last item in the list will override any previous ones in a fixture name collision -export const test = mergeTests(authenticateTest, electronTest); +export const test = mergeTests(envTest, authenticateTest, electronTest); export { expect } from '@playwright/test'; diff --git a/e2e-tests/desktop/fixtures/electronTest.js b/e2e-tests/desktop/fixtures/electronTest.js index 729f656bf7..0866dced36 100644 --- a/e2e-tests/desktop/fixtures/electronTest.js +++ b/e2e-tests/desktop/fixtures/electronTest.js @@ -13,7 +13,13 @@ import fs from 'fs'; * @return {string} */ const getExecutablePath = (buildDirectory = 'out') => { - const rootDir = path.resolve('../ui/desktop/electron-app'); + // Using process.cwd() can change depending on where you run the tests so we use the current file location + // __dirname isn't available in ES modules so we need to indirectly get the directory + const rootDir = path.resolve( + import.meta.dirname, + '../../../ui/desktop/electron-app', + ); + // directory where the builds are stored const outDir = path.resolve(rootDir, buildDirectory); // list of files in the out directory diff --git a/e2e-tests/desktop/playwright.config.js b/e2e-tests/desktop/playwright.config.js index f0f9fae6a1..726c5da4f6 100644 --- a/e2e-tests/desktop/playwright.config.js +++ b/e2e-tests/desktop/playwright.config.js @@ -6,6 +6,17 @@ import { defineConfig } from '@playwright/test'; /** @type {import('@playwright/test').PlaywrightTestConfig} */ export default defineConfig({ + globalSetup: '../global-setup', outputDir: './artifacts', workers: 1, // Tests need to be run in serial, otherwise there may be conflicts when using the CLI + use: { + baseURL: process.env.BOUNDARY_ADDR, + extraHTTPHeaders: { + // This token is set in global-setup.js + Authorization: `Bearer ${process.env.E2E_TOKEN}`, + }, + }, + expect: { + timeout: 10000, + }, }); diff --git a/e2e-tests/desktop/tests/authentication.spec.js b/e2e-tests/desktop/tests/authentication.spec.js index e04ca9a8a1..c57fd6377e 100644 --- a/e2e-tests/desktop/tests/authentication.spec.js +++ b/e2e-tests/desktop/tests/authentication.spec.js @@ -44,7 +44,8 @@ test.describe('user/password authentication tests', async () => { }); }); -test.describe('LDAP authentication tests', async () => { +// Setup an LDAP server for these tests +test.fixme('LDAP authentication tests', async () => { test('Authenticates and signs out', async ({ electronPage, clusterUrl, diff --git a/e2e-tests/desktop/tests/scope.spec.js b/e2e-tests/desktop/tests/scope.spec.js new file mode 100644 index 0000000000..00b78c14c8 --- /dev/null +++ b/e2e-tests/desktop/tests/scope.spec.js @@ -0,0 +1,92 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { expect, test } from '../fixtures/baseTest.js'; +import * as boundaryHttp from '../../helpers/boundary-http.js'; + +let orgA; +let projectA; +let targetA; + +let orgB; +let projectB; +let targetB; + +test.beforeEach(async ({ request, targetAddress, targetPort }) => { + // Group A resources + orgA = await boundaryHttp.createOrg(request); + projectA = await boundaryHttp.createProject(request, orgA.id); + targetA = await boundaryHttp.createTarget(request, { + scopeId: projectA.id, + type: 'tcp', + port: targetPort, + address: targetAddress, + }); + + // Group B resources + orgB = await boundaryHttp.createOrg(request); + projectB = await boundaryHttp.createProject(request, orgB.id); + targetB = await boundaryHttp.createTarget(request, { + scopeId: projectB.id, + type: 'tcp', + port: targetPort, + address: targetAddress, + }); +}); + +test.afterEach(async ({ request }) => { + if (orgA) { + await boundaryHttp.deleteOrg(request, orgA.id); + } + + if (orgB) { + await boundaryHttp.deleteOrg(request, orgB.id); + } +}); + +test.describe('Scope tests', async () => { + test('Shows the filtered targets based on selected scope', async ({ + authedPage, + }) => { + const headerNavLocator = await authedPage.getByLabel('header-nav'); + await expect(headerNavLocator).toBeVisible(); + await expect(headerNavLocator.locator('summary')).toHaveText('Global'); + + await expect( + authedPage.getByRole('link', { name: targetA.name }), + ).toBeVisible(); + await expect( + authedPage.getByRole('link', { name: targetB.name }), + ).toBeVisible(); + + await headerNavLocator.click(); + const orgAHeaderNavLink = await authedPage.getByRole('link', { + name: orgA.name, + }); + await orgAHeaderNavLink.click(); + + await expect(headerNavLocator.locator('summary')).toHaveText(orgA.name); + await expect( + authedPage.getByRole('link', { name: targetA.name }), + ).toBeVisible(); + await expect( + authedPage.getByRole('link', { name: targetB.name }), + ).not.toBeVisible(); + + await headerNavLocator.click(); + const orgBHeaderNavLink = await authedPage.getByRole('link', { + name: orgB.name, + }); + await orgBHeaderNavLink.click(); + + await expect(headerNavLocator.locator('summary')).toHaveText(orgB.name); + await expect( + authedPage.getByRole('link', { name: targetB.name }), + ).toBeVisible(); + await expect( + authedPage.getByRole('link', { name: targetA.name }), + ).not.toBeVisible(); + }); +}); diff --git a/e2e-tests/desktop/tests/sessions.spec.js b/e2e-tests/desktop/tests/sessions.spec.js new file mode 100644 index 0000000000..5692774d9b --- /dev/null +++ b/e2e-tests/desktop/tests/sessions.spec.js @@ -0,0 +1,251 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { expect, test } from '../fixtures/baseTest.js'; +import * as boundaryHttp from '../../helpers/boundary-http.js'; + +let org; +let targetWithHost; +let sshTarget; +let sshTarget2; +let credential; + +test.beforeEach( + async ({ request, targetAddress, targetPort, sshUser, sshKeyPath }) => { + org = await boundaryHttp.createOrg(request); + const project = await boundaryHttp.createProject(request, org.id); + + // Create host set + const hostCatalog = await boundaryHttp.createStaticHostCatalog( + request, + project.id, + ); + const host = await boundaryHttp.createHost(request, { + hostCatalogId: hostCatalog.id, + address: targetAddress, + }); + + const hostSet = await boundaryHttp.createHostSet(request, hostCatalog.id); + await boundaryHttp.addHostToHostSet(request, { + hostSet, + hostIds: [host.id], + }); + + // Create a static credential store and key pair credential + const credentialStore = await boundaryHttp.createStaticCredentialStore( + request, + project.id, + ); + credential = await boundaryHttp.createStaticCredentialKeyPair(request, { + credentialStoreId: credentialStore.id, + username: sshUser, + sshKeyPath, + }); + + // Create tcp target with host set and 1 host + targetWithHost = await boundaryHttp.createTarget(request, { + scopeId: project.id, + type: 'tcp', + port: targetPort, + }); + targetWithHost = await boundaryHttp.addHostSource(request, { + target: targetWithHost, + hostSourceIds: [hostSet.id], + }); + targetWithHost = await boundaryHttp.addBrokeredCredentials(request, { + target: targetWithHost, + credentialIds: [credential.id], + }); + + // Create SSH targets with address + sshTarget = await boundaryHttp.createTarget(request, { + scopeId: project.id, + type: 'ssh', + port: targetPort, + address: targetAddress, + }); + sshTarget = await boundaryHttp.addInjectedCredentials(request, { + target: sshTarget, + credentialIds: [credential.id], + }); + + sshTarget2 = await boundaryHttp.createTarget(request, { + scopeId: project.id, + type: 'ssh', + port: targetPort, + address: targetAddress, + }); + sshTarget2 = await boundaryHttp.addInjectedCredentials(request, { + target: sshTarget2, + credentialIds: [credential.id], + }); + }, +); + +test.afterEach(async ({ request }) => { + if (org) { + await boundaryHttp.deleteOrg(request, org.id); + } +}); + +test.describe('Sessions tests', async () => { + test('Establishes two different sessions and cancels them', async ({ + authedPage, + }) => { + // Connect to two targets + await authedPage + .getByRole('row', { name: sshTarget.name }) + .getByRole('link', { name: 'Connect' }) + .click(); + await expect( + authedPage.getByRole('heading', { name: 'Sessions' }), + ).toBeVisible(); + + await authedPage.getByRole('link', { name: 'Targets' }).click(); + + await authedPage + .getByRole('row', { name: targetWithHost.name }) + .getByRole('link', { name: 'Connect' }) + .click(); + await expect( + authedPage.getByRole('heading', { name: 'Sessions' }), + ).toBeVisible(); + + await authedPage.getByRole('link', { name: 'Sessions' }).click(); + + await expect( + authedPage + .getByRole('row') + .filter({ hasNot: authedPage.getByRole('columnheader') }) + .getByText('Pending'), + ).toHaveCount(2); + + // Cancel both sessions + for (const cancel of await authedPage.getByLabel('Cancel').all()) { + await cancel.click(); + } + + await expect( + authedPage + .getByRole('row') + .filter({ hasNot: authedPage.getByRole('columnheader') }) + .getByText('Canceling'), + ).toHaveCount(2); + }); +}); + +test.describe('Filtering sessions tests', async () => { + test.beforeEach(async ({ authedPage }) => { + // Connect to three targets + await authedPage + .getByRole('row', { name: sshTarget.name }) + .getByRole('link', { name: 'Connect' }) + .click(); + await expect( + authedPage.getByRole('heading', { name: 'Sessions' }), + ).toBeVisible(); + + await authedPage.getByRole('link', { name: 'Targets' }).click(); + + await authedPage + .getByRole('row', { name: sshTarget2.name }) + .getByRole('link', { name: 'Connect' }) + .click(); + await expect( + authedPage.getByRole('heading', { name: 'Sessions' }), + ).toBeVisible(); + + await authedPage.getByRole('link', { name: 'Targets' }).click(); + + await authedPage + .getByRole('row', { name: targetWithHost.name }) + .getByRole('link', { name: 'Connect' }) + .click(); + await expect( + authedPage.getByRole('heading', { name: 'Sessions' }), + ).toBeVisible(); + await authedPage.getByRole('link', { name: 'Sessions' }).click(); + + await expect( + authedPage + .getByRole('row') + .filter({ hasNot: authedPage.getByRole('columnheader') }) + .getByRole('cell', { name: 'Pending' }), + ).toHaveCount(3); + }); + + test.afterEach(async ({ authedPage }) => { + await authedPage.getByRole('button', { name: 'Clear Filters' }).click(); + await expect( + authedPage.getByRole('button', { name: 'Clear Filters' }), + ).toBeHidden(); + for (const cancel of await authedPage.getByLabel('Cancel').all()) { + await cancel.click(); + } + + await expect(authedPage.getByLabel('Cancel')).toHaveCount(0); + }); + + test('Filters by target', async ({ authedPage }) => { + await authedPage.getByRole('button', { name: 'Target' }).click(); + await authedPage.getByLabel('Narrow results').fill(targetWithHost.name); + await expect(authedPage.getByRole('checkbox')).toHaveCount(1); + await authedPage.getByLabel(targetWithHost.name).check(); + await authedPage.getByRole('button', { name: 'Apply' }).click(); + + await expect( + authedPage + .getByRole('row') + .filter({ hasNot: authedPage.getByRole('columnheader') }) + .getByRole('cell', { name: 'Pending' }), + ).toHaveCount(1); + await expect( + authedPage.getByRole('cell', { name: targetWithHost.name }), + ).toBeVisible(); + }); + + test('Filters by status', async ({ authedPage }) => { + await authedPage + .getByRole('row', { name: sshTarget.name }) + .getByRole('link') + .first() + .click(); + await authedPage.getByRole('tab', { name: 'Shell' }).click(); + await authedPage.waitForTimeout(3000); + await authedPage.getByRole('link', { name: 'Sessions' }).click(); + + await authedPage.getByRole('button', { name: 'Clear Filters' }).click(); + await expect( + authedPage.getByRole('button', { name: 'Clear Filters' }), + ).toBeHidden(); + await authedPage.getByRole('button', { name: 'Status' }).click(); + await authedPage.getByLabel('Active').check(); + await authedPage.getByRole('button', { name: 'Apply' }).click(); + + await expect( + authedPage + .getByRole('row') + .filter({ hasNot: authedPage.getByRole('columnheader') }) + .getByRole('cell', { name: 'Active' }), + ).toHaveCount(1); + await expect( + authedPage.getByRole('cell', { name: sshTarget.name }), + ).toBeVisible(); + + await authedPage.getByRole('button', { name: 'Clear Filters' }).click(); + await expect( + authedPage.getByRole('button', { name: 'Clear Filters' }), + ).toBeHidden(); + await authedPage.getByRole('button', { name: 'Status' }).click(); + await authedPage.getByLabel('Pending').check(); + await authedPage.getByRole('button', { name: 'Apply' }).click(); + await expect( + authedPage + .getByRole('row') + .filter({ hasNot: authedPage.getByRole('columnheader') }) + .getByRole('cell', { name: 'Pending' }), + ).toHaveCount(2); + }); +}); diff --git a/e2e-tests/desktop/tests/targets.spec.js b/e2e-tests/desktop/tests/targets.spec.js index eecfd299e3..b8b532cc75 100644 --- a/e2e-tests/desktop/tests/targets.spec.js +++ b/e2e-tests/desktop/tests/targets.spec.js @@ -4,33 +4,247 @@ */ import { expect, test } from '../fixtures/baseTest.js'; +import * as boundaryHttp from '../../helpers/boundary-http.js'; + +const hostName = 'Host name for test'; +let org; +let targetWithHost; +let targetWithTwoHosts; +let sshTarget; +let credential; + +test.beforeEach( + async ({ request, targetAddress, targetPort, sshUser, sshKeyPath }) => { + org = await boundaryHttp.createOrg(request); + const project = await boundaryHttp.createProject(request, org.id); + + // Create host set + const hostCatalog = await boundaryHttp.createStaticHostCatalog( + request, + project.id, + ); + const host = await boundaryHttp.createHost(request, { + hostCatalogId: hostCatalog.id, + address: targetAddress, + }); + const host2 = await boundaryHttp.createHost(request, { + hostCatalogId: hostCatalog.id, + address: targetAddress, + name: hostName, + }); + + const hostSet = await boundaryHttp.createHostSet(request, hostCatalog.id); + await boundaryHttp.addHostToHostSet(request, { + hostSet, + hostIds: [host.id], + }); + + // Create a host set with 2 hosts + let hostSetWithTwoHosts = await boundaryHttp.createHostSet( + request, + hostCatalog.id, + ); + hostSetWithTwoHosts = await boundaryHttp.addHostToHostSet(request, { + hostSet: hostSetWithTwoHosts, + hostIds: [host.id], + }); + hostSetWithTwoHosts = await boundaryHttp.addHostToHostSet(request, { + hostSet: hostSetWithTwoHosts, + hostIds: [host2.id], + }); + + // Create a static credential store and key pair credential + const credentialStore = await boundaryHttp.createStaticCredentialStore( + request, + project.id, + ); + credential = await boundaryHttp.createStaticCredentialKeyPair(request, { + credentialStoreId: credentialStore.id, + username: sshUser, + sshKeyPath, + }); + + // Create tcp target with host set and 1 host + targetWithHost = await boundaryHttp.createTarget(request, { + scopeId: project.id, + type: 'tcp', + port: targetPort, + }); + targetWithHost = await boundaryHttp.addHostSource(request, { + target: targetWithHost, + hostSourceIds: [hostSet.id], + }); + targetWithHost = await boundaryHttp.addBrokeredCredentials(request, { + target: targetWithHost, + credentialIds: [credential.id], + }); + + // Create tcp target with host set and 2 hosts + targetWithTwoHosts = await boundaryHttp.createTarget(request, { + scopeId: project.id, + type: 'tcp', + port: targetPort, + }); + targetWithTwoHosts = await boundaryHttp.addHostSource(request, { + target: targetWithTwoHosts, + hostSourceIds: [hostSetWithTwoHosts.id], + }); + targetWithTwoHosts = await boundaryHttp.addBrokeredCredentials(request, { + target: targetWithTwoHosts, + credentialIds: [credential.id], + }); + + // Create an SSH target with address + sshTarget = await boundaryHttp.createTarget(request, { + scopeId: project.id, + type: 'ssh', + port: targetPort, + address: targetAddress, + }); + sshTarget = await boundaryHttp.addInjectedCredentials(request, { + target: sshTarget, + credentialIds: [credential.id], + }); + }, +); + +test.afterEach(async ({ request }) => { + if (org) { + await boundaryHttp.deleteOrg(request, org.id); + } +}); + +test.describe('Targets tests', async () => { + test('Connects to a tcp target with one host', async ({ authedPage }) => { + await authedPage.getByRole('link', { name: targetWithHost.name }).click(); + await authedPage.getByRole('button', { name: 'Connect' }).click(); + + await expect( + authedPage.getByRole('heading', { name: 'Sessions' }), + ).toBeVisible(); + + await expect(authedPage.getByText(credential.name)).toBeVisible(); + await expect(authedPage.getByText('username')).toBeVisible(); + await expect(authedPage.getByText('private_key')).toBeVisible(); + + await authedPage + .getByRole('listitem') + .filter({ hasText: 'username' }) + .getByLabel('Toggle secret visibility') + .click(); + await expect( + authedPage + .getByRole('listitem') + .filter({ hasText: 'username' }) + .locator('pre'), + ).toHaveText(credential.attributes.username); -test.describe('Targets test', async () => { - test('Connects to a target and ends session', async ({ authedPage }) => { await authedPage - .getByRole('link', { name: 'Generated target with a direct address' }) + .getByRole('listitem') + .filter({ hasText: 'private_key' }) + .getByLabel('Toggle secret visibility') .click(); + await expect( + authedPage + .getByRole('listitem') + .filter({ hasText: 'private_key' }) + .locator('pre'), + ).toContainText(/BEGIN (OPENSSH|RSA) PRIVATE KEY/); + await authedPage.getByRole('button', { name: 'End Session' }).click(); + await expect(authedPage.getByText('Canceled successfully.')).toBeVisible(); + await expect( + authedPage.getByRole('heading', { name: 'Targets' }), + ).toBeVisible(); + }); + + [{ host: 'Quick Connect' }, { host: hostName }].forEach(({ host }) => { + test(`Connects to a tcp target with two hosts using ${host}`, async ({ + authedPage, + }) => { + await authedPage + .getByRole('link', { name: targetWithTwoHosts.name }) + .click(); + await authedPage.getByRole('button', { name: 'Connect' }).click(); + + await expect( + authedPage.getByRole('heading', { name: 'Choose a Host' }), + ).toBeVisible(); + + await authedPage.getByRole('button', { name: host }).click(); + + await expect( + authedPage.getByRole('heading', { name: 'Sessions' }), + ).toBeVisible(); + + await expect(authedPage.getByText(credential.name)).toBeVisible(); + await expect(authedPage.getByText('username')).toBeVisible(); + await expect(authedPage.getByText('private_key')).toBeVisible(); + + await authedPage + .getByRole('listitem') + .filter({ hasText: 'username' }) + .getByLabel('Toggle secret visibility') + .click(); + await expect( + authedPage + .getByRole('listitem') + .filter({ hasText: 'username' }) + .locator('pre'), + ).toHaveText(credential.attributes.username); + + await authedPage + .getByRole('listitem') + .filter({ hasText: 'private_key' }) + .getByLabel('Toggle secret visibility') + .click(); + await expect( + authedPage + .getByRole('listitem') + .filter({ hasText: 'private_key' }) + .locator('pre'), + ).toContainText(/BEGIN (OPENSSH|RSA) PRIVATE KEY/); + + await authedPage.getByRole('button', { name: 'End Session' }).click(); + await expect( + authedPage.getByText('Canceled successfully.'), + ).toBeVisible(); + await expect(authedPage).toHaveURL( + /.*\/scopes\/global\/projects\/targets$/, + ); + }); + }); + + test('Connects to an SSH target', async ({ authedPage }) => { + await authedPage.getByRole('link', { name: sshTarget.name }).click(); await authedPage.getByRole('button', { name: 'Connect' }).click(); - await expect(authedPage).toHaveURL( - /.*\/scopes\/global\/projects\/sessions/, - ); + await expect( + authedPage.getByRole('heading', { name: 'Sessions' }), + ).toBeVisible(); + + await authedPage.getByRole('tab', { name: 'Shell' }).click(); + // TODO: Research a better way to test canvas elements for the shell, + // would it be too brittle to assert a snapshot of an expected image? await authedPage.getByRole('button', { name: 'End Session' }).click(); await expect(authedPage.getByText('Canceled successfully.')).toBeVisible(); - await expect(authedPage).toHaveURL( - /.*\/scopes\/global\/projects\/targets$/, - ); + + await expect( + authedPage.getByRole('heading', { name: 'Targets' }), + ).toBeVisible(); }); test('Searches targets correctly', async ({ authedPage }) => { - await authedPage.getByLabel('Search').fill('hashicorp'); + await authedPage.getByLabel('Search').fill(targetWithHost.name); - // One row is the header - await expect(authedPage.getByRole('row')).toHaveCount(2); await expect( - authedPage.getByRole('link', { name: 'www.hashicorp.com' }), + authedPage + .getByRole('row') + .filter({ hasNot: authedPage.getByRole('columnheader') }), + ).toHaveCount(1); + await expect( + authedPage.getByRole('link', { name: targetWithHost.name }), ).toBeVisible(); }); }); diff --git a/e2e-tests/global-setup.js b/e2e-tests/global-setup.js new file mode 100644 index 0000000000..bf8560c558 --- /dev/null +++ b/e2e-tests/global-setup.js @@ -0,0 +1,88 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { chromium, test as baseTest } from '@playwright/test'; +import { checkEnv } from './helpers/general.js'; +import { LoginPage } from './admin/pages/login.js'; +import path from 'path'; +import dotenv from 'dotenv'; + +const __dirname = import.meta.dirname; + +// Import environment variables from .env file if available +dotenv.config({ path: path.resolve(__dirname, './.env') }); + +async function globalSetup() { + await checkEnv([ + 'BOUNDARY_ADDR', + 'E2E_PASSWORD_ADMIN_LOGIN_NAME', + 'E2E_PASSWORD_ADMIN_PASSWORD', + 'E2E_PASSWORD_AUTH_METHOD_ID', + ]); + await authenticateToBoundary(); +} + +const authenticateToBoundary = async () => { + // Log in and save the authenticated state to reuse in tests + const browser = await chromium.launch(); + const page = await browser.newPage(); + await page.goto(process.env.BOUNDARY_ADDR); + + const loginPage = new LoginPage(page); + await loginPage.login( + process.env.E2E_PASSWORD_ADMIN_LOGIN_NAME, + process.env.E2E_PASSWORD_ADMIN_PASSWORD, + ); + const storageState = await page + .context() + .storageState({ path: authenticatedState }); + + const state = JSON.parse(storageState.origins[0].localStorage[0].value); + + // Set the token in the environment for use in API requests + process.env.E2E_TOKEN = state.authenticated.token; + + await browser.close(); +}; + +export default globalSetup; + +export const authenticatedState = path.resolve( + __dirname, + './.auth/authenticated-state.json', +); + +// Centralized location for environment variables used in tests +export const test = baseTest.extend({ + adminAuthMethodId: process.env.E2E_PASSWORD_AUTH_METHOD_ID, + adminLoginName: process.env.E2E_PASSWORD_ADMIN_LOGIN_NAME, + adminPassword: process.env.E2E_PASSWORD_ADMIN_PASSWORD, + awsAccessKeyId: process.env.E2E_AWS_ACCESS_KEY_ID, + awsBucketName: process.env.E2E_AWS_BUCKET_NAME, + awsHostSetFilter: process.env.E2E_AWS_HOST_SET_FILTER, + awsHostSetIps: process.env.E2E_AWS_HOST_SET_IPS, + awsRegion: process.env.E2E_AWS_REGION, + awsSecretAccessKey: process.env.E2E_AWS_SECRET_ACCESS_KEY, + bucketAccessKeyId: process.env.E2E_BUCKET_ACCESS_KEY_ID, + bucketEndpointUrl: process.env.E2E_BUCKET_ENDPOINT_URL, + bucketName: process.env.E2E_BUCKET_NAME, + bucketSecretAccessKey: process.env.E2E_BUCKET_SECRET_ACCESS_KEY, + ldapAddr: process.env.E2E_LDAP_ADDR, + ldapAdminDn: process.env.E2E_LDAP_ADMIN_DN, + ldapAdminPassword: process.env.E2E_LDAP_ADMIN_PASSWORD, + ldapDomainDn: process.env.E2E_LDAP_DOMAIN_DN, + ldapGroupName: process.env.E2E_LDAP_GROUP_NAME, + ldapUserName: process.env.E2E_LDAP_USER_NAME, + ldapUserPassword: process.env.E2E_LDAP_USER_PASSWORD, + region: process.env.E2E_REGION, + sshCaKey: process.env.E2E_SSH_CA_KEY, + sshCaKeyPublic: process.env.E2E_SSH_CA_KEY_PUBLIC, + sshKeyPath: process.env.E2E_SSH_KEY_PATH, + sshUser: process.env.E2E_SSH_USER, + targetAddress: process.env.E2E_TARGET_ADDRESS, + targetPort: process.env.E2E_TARGET_PORT, + vaultAddr: process.env.E2E_VAULT_ADDR, + workerTagEgress: process.env.E2E_WORKER_TAG_EGRESS, +}); diff --git a/e2e-tests/helpers/boundary-cli/accounts.js b/e2e-tests/helpers/boundary-cli/accounts.js index cb6f2d7d2a..24f37242e6 100644 --- a/e2e-tests/helpers/boundary-cli/accounts.js +++ b/e2e-tests/helpers/boundary-cli/accounts.js @@ -10,7 +10,7 @@ import { execSync } from 'node:child_process'; * @param {string} authMethodId ID of the auth-method that the new account will be created for. * @returns {Promise} new account's ID */ -export async function createPasswordAccountCli(authMethodId) { +export async function createPasswordAccount(authMethodId) { let passwordAccount; const login = 'test-login'; try { diff --git a/e2e-tests/helpers/boundary-cli/aliases.js b/e2e-tests/helpers/boundary-cli/aliases.js index 5daf6147c0..c7774b2642 100644 --- a/e2e-tests/helpers/boundary-cli/aliases.js +++ b/e2e-tests/helpers/boundary-cli/aliases.js @@ -9,7 +9,7 @@ import { execSync } from 'node:child_process'; * Deletes the specified alias * @param {string} aliasValue Value of the alias to be deleted */ -export async function deleteAliasCli(aliasValue) { +export async function deleteAlias(aliasValue) { try { const aliases = JSON.parse(execSync('boundary aliases list -format json')); const alias = aliases.items.filter((obj) => obj.value == aliasValue)[0]; diff --git a/e2e-tests/helpers/boundary-cli/auth-methods.js b/e2e-tests/helpers/boundary-cli/auth-methods.js index 6a24c135fd..31008b414c 100644 --- a/e2e-tests/helpers/boundary-cli/auth-methods.js +++ b/e2e-tests/helpers/boundary-cli/auth-methods.js @@ -11,7 +11,7 @@ import { nanoid } from 'nanoid'; * @param {string} scopeId ID of the scope under which the auth-method will be created. * @returns {Promise} new auth-method's ID */ -export async function createPasswordAuthMethodCli(scopeId) { +export async function createPasswordAuthMethod(scopeId) { const authMethodName = 'auth-method-' + nanoid(); let newAuthMethod; try { diff --git a/e2e-tests/helpers/boundary-cli/authenticate.js b/e2e-tests/helpers/boundary-cli/authenticate.js index fbbbc64f22..dccd67bd2e 100644 --- a/e2e-tests/helpers/boundary-cli/authenticate.js +++ b/e2e-tests/helpers/boundary-cli/authenticate.js @@ -12,7 +12,7 @@ import { execSync } from 'node:child_process'; * @param {string} loginName Login name to be used for authentication * @param {string} password Password to be used for authentication */ -export async function authenticateBoundaryCli( +export async function authenticateBoundary( addr, authMethodId, loginName, diff --git a/e2e-tests/helpers/boundary-cli/connect.js b/e2e-tests/helpers/boundary-cli/connect.js index 4a22bfdcdc..0632105990 100644 --- a/e2e-tests/helpers/boundary-cli/connect.js +++ b/e2e-tests/helpers/boundary-cli/connect.js @@ -3,7 +3,50 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { exec } from 'node:child_process'; +import { spawn } from 'node:child_process'; + +const remoteCommand = + 'for i in {1..15}; do echo helloworld\\$i; sleep 1s; done'; + +/** + * Spawns a child process for a Boundary connection and returns a promise. + * Resolves on any output from stdout or stderr. + * @param {string} command + * @param {Array} args + */ +async function spawnConnection(command, args) { + return new Promise((resolve, reject) => { + const childProcess = spawn(command, args, { + shell: true, + }); + childProcess.stdout.on('data', (data) => { + if (data.toString().includes('helloworld3')) { + resolve({ childProcess, stdout: data.toString() }); + } + }); + childProcess.stderr.on('data', (data) => { + if (!data.toString().includes('Warning: Permanently added')) { + reject(data.toString()); + } + }); + childProcess.on('error', (err) => { + reject(err); + }); + + // In case the process has no stdio and didn't error out, resolve a + // promise once the child process closes so we're not waiting forever. + // Windows doesn't seem to return any error nor any output from stderr when + // a timeout occurs so this guarantees we return a response to the caller. + // Otherwise this should not get hit as we should be returning a response + // from one of the handlers above. + childProcess.on('close', () => { + resolve({ + childProcess, + stderr: JSON.stringify({ error: 'Process was closed.' }), + }); + }); + }); +} /** * Connects to the specified target @@ -14,23 +57,61 @@ import { exec } from 'node:child_process'; * @returns ChildProcess representing the result of the command execution */ export async function connectToTarget(targetId, sshUser, sshKeyPath) { - let connect; try { - connect = exec( - `boundary connect -target-id=${targetId}` + - ' -exec /usr/bin/ssh --' + - ` -l ${sshUser}` + - ` -i ${sshKeyPath}` + - ' -o UserKnownHostsFile=/dev/null' + - ' -o StrictHostKeyChecking=no' + - ' -o IdentitiesOnly=yes' + // forces the use of the provided key - ' -p {{boundary.port}}' + - ' {{boundary.ip}}', - ); + const { childProcess } = await spawnConnection('boundary', [ + 'connect', + `-target-id=${targetId}`, + '-exec', + '/usr/bin/ssh', + '--', + '-T', // forces tty allocation + `-l ${sshUser}`, + `-i ${sshKeyPath}`, + '-o UserKnownHostsFile=/dev/null', + '-o StrictHostKeyChecking=no', + '-o IdentitiesOnly=yes', // forces the use of the provided key + '-p {{boundary.port}}', + '{{boundary.ip}}', + `"${remoteCommand}"`, + ]); + + return childProcess; + } catch (e) { + throw new Error(`Failed to connect to the target: ${e}`); + } +} + +/** + * Connects to the specified alias + * exec is used here to keep the session open + * @param {string} alias alias of the target to be connected to + * @param {string} sshUser User to be used for the ssh connection + * @param {string} sshKeyPath Path to the ssh key to be used for the ssh connection + * @returns ChildProcess representing the result of the command execution + */ +export async function connectToAlias(alias, sshUser, sshKeyPath) { + try { + const { childProcess } = await spawnConnection('boundary', [ + 'connect', + `${alias}`, + '-exec', + '/usr/bin/ssh', + '--', + '-T', // forces tty allocation + `-l ${sshUser}`, + `-i ${sshKeyPath}`, + '-o UserKnownHostsFile=/dev/null', + '-o StrictHostKeyChecking=no', + '-o IdentitiesOnly=yes', // forces the use of the provided key + '-p {{boundary.port}}', + '{{boundary.ip}}', + `"${remoteCommand}"`, + ]); + + return childProcess; } catch (e) { - console.log(`${e.stderr}`); + throw new Error(`Failed to connect to the target: ${e}`); } - return connect; } /** @@ -40,18 +121,47 @@ export async function connectToTarget(targetId, sshUser, sshKeyPath) { * @returns ChildProcess representing the result of the command execution */ export async function connectSshToTarget(targetId) { - let connect; try { - connect = exec( - 'boundary connect ssh' + - ` -target-id=${targetId}` + - ' --' + - ' -o UserKnownHostsFile=/dev/null' + - ' -o StrictHostKeyChecking=no' + - ' -o IdentitiesOnly=yes', // forces the use of the provided key - ); + const { childProcess } = await spawnConnection('boundary', [ + 'connect', + 'ssh', + `-target-id=${targetId}`, + `-remote-command="${remoteCommand}"`, + '--', + '-T', // forces tty allocation + '-o UserKnownHostsFile=/dev/null', + '-o StrictHostKeyChecking=no', + '-o IdentitiesOnly=yes', // forces the use of the provided key + ]); + + return childProcess; + } catch (e) { + throw new Error(`Failed to connect to the target: ${e}`); + } +} + +/** + * Connects via ssh to the specified alias + * exec is used here to keep the session open + * @param {string} alias alias of the target to be connected to + * @returns ChildProcess representing the result of the command execution + */ +export async function connectSshToAlias(alias) { + try { + const { childProcess } = await spawnConnection('boundary', [ + 'connect', + `ssh`, + `${alias}`, + `-remote-command="${remoteCommand}"`, + '--', + '-T', // forces tty allocation + '-o UserKnownHostsFile=/dev/null', + '-o StrictHostKeyChecking=no', + '-o IdentitiesOnly=yes', // forces the use of the provided key + ]); + + return childProcess; } catch (e) { - console.log(`${e.stderr}`); + throw new Error(`Failed to connect to the target: ${e}`); } - return connect; } diff --git a/e2e-tests/helpers/boundary-cli/credential-stores.js b/e2e-tests/helpers/boundary-cli/credential-stores.js index 73126eee90..598afd9e50 100644 --- a/e2e-tests/helpers/boundary-cli/credential-stores.js +++ b/e2e-tests/helpers/boundary-cli/credential-stores.js @@ -11,7 +11,7 @@ import { nanoid } from 'nanoid'; * @param {string} projectId ID of the project under which the credential store will be created. * @returns {Promise} new credential store's ID */ -export async function createStaticCredentialStoreCli(projectId) { +export async function createStaticCredentialStore(projectId) { const credentialStoreName = 'static-credential-store-' + nanoid(); let staticCredentialStore; try { @@ -36,7 +36,7 @@ export async function createStaticCredentialStoreCli(projectId) { * @param {string} vaultToken Token for Boundary to authenticate with Vault * @returns {Promise} new credential store's ID */ -export async function createVaultCredentialStoreCli( +export async function createVaultCredentialStore( projectId, vaultAddr, vaultToken, diff --git a/e2e-tests/helpers/boundary-cli/credentials.js b/e2e-tests/helpers/boundary-cli/credentials.js index 4a5dafd1b7..41600c9ed9 100644 --- a/e2e-tests/helpers/boundary-cli/credentials.js +++ b/e2e-tests/helpers/boundary-cli/credentials.js @@ -10,7 +10,7 @@ import { execSync } from 'node:child_process'; * @param {string} credentialStoreId ID of the credential store that the credential will be created for. * @returns {Promise} new credential's ID */ -export async function createUsernamePasswordCredentialCli(credentialStoreId) { +export async function createUsernamePasswordCredential(credentialStoreId) { let usernamePasswordCredential; const login = 'test-login'; try { diff --git a/e2e-tests/helpers/boundary-cli/groups.js b/e2e-tests/helpers/boundary-cli/groups.js index 200fc7f32f..4a75f2daff 100644 --- a/e2e-tests/helpers/boundary-cli/groups.js +++ b/e2e-tests/helpers/boundary-cli/groups.js @@ -11,7 +11,7 @@ import { nanoid } from 'nanoid'; * @param {string} scopeId ID of the scope under which the group will be created. * @returns {Promise} new group's ID */ -export async function createGroupCli(scopeId) { +export async function createGroup(scopeId) { const groupName = 'group-' + nanoid(); let group; try { diff --git a/e2e-tests/helpers/boundary-cli/host-catalogs.js b/e2e-tests/helpers/boundary-cli/host-catalogs.js index 25bf899013..977074a89d 100644 --- a/e2e-tests/helpers/boundary-cli/host-catalogs.js +++ b/e2e-tests/helpers/boundary-cli/host-catalogs.js @@ -11,7 +11,7 @@ import { nanoid } from 'nanoid'; * @param {string} projectId ID of the project under which the host catalog will be created. * @returns {Promise} new host catalog's ID */ -export async function createStaticHostCatalogCli(projectId) { +export async function createStaticHostCatalog(projectId) { const hostCatalogName = 'static-host-catalog-' + nanoid(); let hostCatalog; try { @@ -35,7 +35,7 @@ export async function createStaticHostCatalogCli(projectId) { * @param {string} region Name of the AWS region that the host catalog will be created for. * @returns {Promise} new host catalog's ID */ -export async function createDynamicAwsHostCatalogCli(projectId, region) { +export async function createDynamicAwsHostCatalog(projectId, region) { const hostCatalogName = 'dynamic-aws-host-catalog-' + nanoid(); let hostCatalog; try { diff --git a/e2e-tests/helpers/boundary-cli/host-sets.js b/e2e-tests/helpers/boundary-cli/host-sets.js index 6d98b1b873..6dec26236c 100644 --- a/e2e-tests/helpers/boundary-cli/host-sets.js +++ b/e2e-tests/helpers/boundary-cli/host-sets.js @@ -11,7 +11,7 @@ import { nanoid } from 'nanoid'; * @param {string} hostCatalogId ID of the host catalog that the host set will be created for. * @returns {Promise} new host set's ID */ -export async function createHostSetCli(hostCatalogId) { +export async function createHostSet(hostCatalogId) { const hostSetName = 'static-host-' + nanoid(); let hostSet; try { diff --git a/e2e-tests/helpers/boundary-cli/hosts.js b/e2e-tests/helpers/boundary-cli/hosts.js index d7bacc9b25..09fa88950b 100644 --- a/e2e-tests/helpers/boundary-cli/hosts.js +++ b/e2e-tests/helpers/boundary-cli/hosts.js @@ -11,7 +11,7 @@ import { nanoid } from 'nanoid'; * @param {string} hostCatalogId ID of the host catalog that the host will be created for. * @returns {Promise} new host's ID */ -export async function createStaticHostCli(hostCatalogId) { +export async function createStaticHost(hostCatalogId) { const hostName = 'static-host-' + nanoid(); let host; try { diff --git a/e2e-tests/helpers/boundary-cli/policies.js b/e2e-tests/helpers/boundary-cli/policies.js index b48d8ce6f4..9777b463fd 100644 --- a/e2e-tests/helpers/boundary-cli/policies.js +++ b/e2e-tests/helpers/boundary-cli/policies.js @@ -9,7 +9,7 @@ import { execSync } from 'node:child_process'; * Deletes the specified policy * @param {string} policyId ID of the policy to be deleted */ -export async function deletePolicyCli(policyId) { +export async function deletePolicy(policyId) { try { execSync('boundary policies delete -id=' + policyId); } catch (e) { @@ -23,7 +23,7 @@ export async function deletePolicyCli(policyId) { * @param {string} policyName Name of the policy * @returns {Promise} ID of the policy */ -export async function getPolicyIdFromNameCli(scopeId, policyName) { +export async function getPolicyIdFromName(scopeId, policyName) { const policies = JSON.parse( execSync(`boundary policies list -scope-id ${scopeId} -format json`), ); diff --git a/e2e-tests/helpers/boundary-cli/roles.js b/e2e-tests/helpers/boundary-cli/roles.js index f4e8cf1812..49a1028da1 100644 --- a/e2e-tests/helpers/boundary-cli/roles.js +++ b/e2e-tests/helpers/boundary-cli/roles.js @@ -11,7 +11,7 @@ import { nanoid } from 'nanoid'; * @param {string} scopeId ID of the scope under which the role will be created. * @returns {Promise} new role's ID */ -export async function createRoleCli(scopeId) { +export async function createRole(scopeId) { const roleName = 'role-' + nanoid(); let role; try { diff --git a/e2e-tests/helpers/boundary-cli/scopes.js b/e2e-tests/helpers/boundary-cli/scopes.js index eb102f259c..2dce87a8fa 100644 --- a/e2e-tests/helpers/boundary-cli/scopes.js +++ b/e2e-tests/helpers/boundary-cli/scopes.js @@ -10,7 +10,7 @@ import { nanoid } from 'nanoid'; * Creates an org * @returns {Promise} new org's id */ -export async function createOrgCli() { +export async function createOrg() { const orgName = 'Org ' + nanoid(); let newOrg; try { @@ -33,7 +33,7 @@ export async function createOrgCli() { * @param {string} orgId ID of the organization under which the project will be created. * @returns {Promise} new project's ID */ -export async function createProjectCli(orgId) { +export async function createProject(orgId) { const projectName = 'Project ' + nanoid(); let newProject; try { @@ -55,7 +55,7 @@ export async function createProjectCli(orgId) { * Deletes the specified scope * @param {string} scopeId ID of the scope to be deleted */ -export async function deleteScopeCli(scopeId) { +export async function deleteScope(scopeId) { try { execSync('boundary scopes delete -id=' + scopeId); } catch (e) { @@ -68,7 +68,7 @@ export async function deleteScopeCli(scopeId) { * @param {string} orgName Name of the org * @returns {Promise} ID of the org */ -export async function getOrgIdFromNameCli(orgName) { +export async function getOrgIdFromName(orgName) { const orgs = JSON.parse(execSync('boundary scopes list -format json')); const org = orgs.items.filter((obj) => obj.name == orgName)[0]; if (org) { @@ -83,7 +83,7 @@ export async function getOrgIdFromNameCli(orgName) { * @param {string} projectName Name of the project * @returns {Promise} ID of the project */ -export async function getProjectIdFromNameCli(orgId, projectName) { +export async function getProjectIdFromName(orgId, projectName) { const projects = JSON.parse( execSync(`boundary scopes list -scope-id ${orgId} -format json`), ); @@ -99,7 +99,7 @@ export async function getProjectIdFromNameCli(orgId, projectName) { * @param {string} scopeId ID of the scope that the auth-method belongs to * @param {string} authMethodId ID of the auth method that will be made primary */ -export async function makeAuthMethodPrimaryCli(scopeId, authMethodId) { +export async function makeAuthMethodPrimary(scopeId, authMethodId) { try { execSync( `boundary scopes update \ diff --git a/e2e-tests/helpers/boundary-cli/session-recordings.js b/e2e-tests/helpers/boundary-cli/session-recordings.js index 54ab2dd463..1ac6b5688a 100644 --- a/e2e-tests/helpers/boundary-cli/session-recordings.js +++ b/e2e-tests/helpers/boundary-cli/session-recordings.js @@ -10,7 +10,7 @@ import { execSync } from 'node:child_process'; * @param {string} storageBucketId ID of storage bucket that the session recording is associated with * @returns An object representing a session recording */ -export async function waitForSessionRecordingCli(storageBucketId) { +export async function waitForSessionRecording(storageBucketId) { let i = 0; let filteredSessionRecording = []; do { diff --git a/e2e-tests/helpers/boundary-cli/storage-buckets.js b/e2e-tests/helpers/boundary-cli/storage-buckets.js index 3abf104d80..052b3174a7 100644 --- a/e2e-tests/helpers/boundary-cli/storage-buckets.js +++ b/e2e-tests/helpers/boundary-cli/storage-buckets.js @@ -9,7 +9,7 @@ import { execSync } from 'node:child_process'; * Deletes the specified storage bucket * @param {string} storageBucketId ID of the storage bucket to be deleted */ -export async function deleteStorageBucketCli(storageBucketId) { +export async function deleteStorageBucket(storageBucketId) { try { execSync('boundary storage-buckets delete -id=' + storageBucketId); } catch (e) { diff --git a/e2e-tests/helpers/boundary-cli/targets.js b/e2e-tests/helpers/boundary-cli/targets.js index dc89f5187d..0b195b618f 100644 --- a/e2e-tests/helpers/boundary-cli/targets.js +++ b/e2e-tests/helpers/boundary-cli/targets.js @@ -36,7 +36,7 @@ export async function createTcpTarget(projectId) { * @param {string} projectId ID of the project under which the target will be created. * @returns {Promise} new target's ID */ -export async function createSshTargetCli(projectId) { +export async function createSshTarget(projectId) { const targetName = 'target-' + nanoid(); const defaultPort = 22; let target; @@ -61,7 +61,7 @@ export async function createSshTargetCli(projectId) { * @param {string} alias alias of the target to be connected to * @returns Session information */ -export async function authorizeSessionByAliasCli(alias) { +export async function authorizeSessionByAlias(alias) { let session; try { session = JSON.parse( @@ -78,7 +78,7 @@ export async function authorizeSessionByAliasCli(alias) { * @param {string} targetId Id of the target to be connected to * @returns Session information */ -export async function authorizeSessionByTargetIdCli(targetId) { +export async function authorizeSessionByTargetId(targetId) { let session; try { session = JSON.parse( @@ -98,7 +98,7 @@ export async function authorizeSessionByTargetIdCli(targetId) { * @param {string} targetName Name of the target * @returns {Promise} ID of the target */ -export async function getTargetIdFromNameCli(projectId, targetName) { +export async function getTargetIdFromName(projectId, targetName) { const targets = JSON.parse( execSync(`boundary targets list -scope-id ${projectId} -format json`), ); diff --git a/e2e-tests/helpers/boundary-cli/users.js b/e2e-tests/helpers/boundary-cli/users.js index 8c2df21568..6ef5a55316 100644 --- a/e2e-tests/helpers/boundary-cli/users.js +++ b/e2e-tests/helpers/boundary-cli/users.js @@ -11,7 +11,7 @@ import { nanoid } from 'nanoid'; * @param {string} scopeId ID of the scope under which the user will be created. * @returns {Promise} new user's ID */ -export async function createUserCli(scopeId) { +export async function createUser(scopeId) { const userName = 'user-' + nanoid(); let user; try { diff --git a/e2e-tests/helpers/boundary-cli/workers.js b/e2e-tests/helpers/boundary-cli/workers.js index 12a4a97567..df81371934 100644 --- a/e2e-tests/helpers/boundary-cli/workers.js +++ b/e2e-tests/helpers/boundary-cli/workers.js @@ -10,7 +10,7 @@ import { nanoid } from 'nanoid'; * Creates a new controller-led worker * @returns {Promise} new worker's ID */ -export async function createControllerLedWorkerCli() { +export async function createControllerLedWorker() { const workerName = 'worker-' + nanoid(); let newWorker; try { diff --git a/e2e-tests/helpers/boundary-http.js b/e2e-tests/helpers/boundary-http.js index 52d76b6316..cd0f974a87 100644 --- a/e2e-tests/helpers/boundary-http.js +++ b/e2e-tests/helpers/boundary-http.js @@ -5,3 +5,8 @@ export * from './boundary-http/scopes.js'; export * from './boundary-http/targets.js'; +export * from './boundary-http/host-catalogs.js'; +export * from './boundary-http/host-sets.js'; +export * from './boundary-http/hosts.js'; +export * from './boundary-http/credential-stores.js'; +export * from './boundary-http/credentials.js'; diff --git a/e2e-tests/helpers/boundary-http/credential-stores.js b/e2e-tests/helpers/boundary-http/credential-stores.js new file mode 100644 index 0000000000..9b58f6dee6 --- /dev/null +++ b/e2e-tests/helpers/boundary-http/credential-stores.js @@ -0,0 +1,25 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { nanoid } from 'nanoid'; +import { checkResponse } from './responseHelper.js'; + +/** + * Creates a new static credential store + * @param {import('@playwright/test').APIRequestContext} request + * @param {string} projectId ID of the project where the credential store will be created + * @returns {Promise} + */ +export async function createStaticCredentialStore(request, projectId) { + const response = await request.post(`/v1/credential-stores`, { + data: { + name: `static-credential-store-${nanoid()}`, + scope_id: projectId, + type: 'static', + }, + }); + + return checkResponse(response); +} diff --git a/e2e-tests/helpers/boundary-http/credentials.js b/e2e-tests/helpers/boundary-http/credentials.js new file mode 100644 index 0000000000..4f407e0da1 --- /dev/null +++ b/e2e-tests/helpers/boundary-http/credentials.js @@ -0,0 +1,39 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { nanoid } from 'nanoid'; +import { checkResponse } from './responseHelper.js'; +import { readFile } from 'fs/promises'; + +/** + * Creates a new ssh keypair credential + * @param {import('@playwright/test').APIRequestContext} request + * @param {string} credentialStoreId ID of the credential store where the credential will be created + * @param {string} username Username of the user credential + * @param {string} sshKeyPath Path to private key of the user credential + * @returns {Promise} + */ +export async function createStaticCredentialKeyPair( + request, + { credentialStoreId, username, sshKeyPath }, +) { + const privateKey = await readFile(sshKeyPath, { + encoding: 'utf-8', + }); + + const response = await request.post(`/v1/credentials`, { + data: { + name: `static-credential-store-${nanoid()}`, + credential_store_id: credentialStoreId, + type: 'ssh_private_key', + attributes: { + username, + private_key: privateKey, + }, + }, + }); + + return checkResponse(response); +} diff --git a/e2e-tests/helpers/boundary-http/host-catalogs.js b/e2e-tests/helpers/boundary-http/host-catalogs.js new file mode 100644 index 0000000000..87a061bf14 --- /dev/null +++ b/e2e-tests/helpers/boundary-http/host-catalogs.js @@ -0,0 +1,25 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { nanoid } from 'nanoid'; +import { checkResponse } from './responseHelper.js'; + +/** + * Creates a new static host catalog + * @param {import('@playwright/test').APIRequestContext} request + * @param {string} projectId ID of the project where the catalog will be created + * @returns {Promise} + */ +export async function createStaticHostCatalog(request, projectId) { + const response = await request.post(`/v1/host-catalogs`, { + data: { + name: `static-host-catalog-${nanoid()}`, + scope_id: projectId, + type: 'static', + }, + }); + + return checkResponse(response); +} diff --git a/e2e-tests/helpers/boundary-http/host-sets.js b/e2e-tests/helpers/boundary-http/host-sets.js new file mode 100644 index 0000000000..7ca811d2f4 --- /dev/null +++ b/e2e-tests/helpers/boundary-http/host-sets.js @@ -0,0 +1,42 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { nanoid } from 'nanoid'; +import { checkResponse } from './responseHelper.js'; + +/** + * Creates a host set + * @param {import('@playwright/test').APIRequestContext} request + * @param {string} hostCatalogId ID of the host catalog + * @returns {Promise} + */ +export async function createHostSet(request, hostCatalogId) { + const response = await request.post(`/v1/host-sets`, { + data: { + name: `static-host-sets-${nanoid()}`, + host_catalog_id: hostCatalogId, + }, + }); + + return checkResponse(response); +} + +/** + * Adds a host + * @param {import('@playwright/test').APIRequestContext} request + * @param {Object} hostSet The host set object + * @param {string} hostIds ID of the host to add + * @returns {Promise} + */ +export async function addHostToHostSet(request, { hostSet, hostIds }) { + const response = await request.post(`/v1/host-sets/${hostSet.id}:add-hosts`, { + data: { + host_ids: hostIds, + version: hostSet.version, + }, + }); + + return checkResponse(response); +} diff --git a/e2e-tests/helpers/boundary-http/hosts.js b/e2e-tests/helpers/boundary-http/hosts.js new file mode 100644 index 0000000000..176123a7c6 --- /dev/null +++ b/e2e-tests/helpers/boundary-http/hosts.js @@ -0,0 +1,30 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { nanoid } from 'nanoid'; +import { checkResponse } from './responseHelper.js'; + +/** + * Creates a host + * @param {import('@playwright/test').APIRequestContext} request + * @param {string} hostCatalogId ID of the host catalog + * @param {string} name Optional name of the host + * @param {string} address Address of the host + * @returns {Promise} + */ +export async function createHost(request, { hostCatalogId, name, address }) { + const response = await request.post(`/v1/hosts`, { + data: { + name: name ?? `static-host-${nanoid()}`, + host_catalog_id: hostCatalogId, + type: 'static', + attributes: { + address: address, + }, + }, + }); + + return checkResponse(response); +} diff --git a/e2e-tests/helpers/boundary-http/responseHelper.js b/e2e-tests/helpers/boundary-http/responseHelper.js new file mode 100644 index 0000000000..77546a1618 --- /dev/null +++ b/e2e-tests/helpers/boundary-http/responseHelper.js @@ -0,0 +1,23 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +/** + * Check if the response is OK + * @param {import('@playwright/test').APIResponse} response + * @param {boolean=} isResponseEmpty + * @return {Promise|void} + */ +export async function checkResponse(response, isResponseEmpty) { + if (!response.ok()) { + throw new Error( + `Request failed with ${response.status()}: ${await response.text()}`, + ); + } + + if (isResponseEmpty) { + return; + } + return await response.json(); +} diff --git a/e2e-tests/helpers/boundary-http/scopes.js b/e2e-tests/helpers/boundary-http/scopes.js index 7cb1cb319e..078e33abe4 100644 --- a/e2e-tests/helpers/boundary-http/scopes.js +++ b/e2e-tests/helpers/boundary-http/scopes.js @@ -4,20 +4,33 @@ */ import { nanoid } from 'nanoid'; +import { checkResponse } from './responseHelper.js'; /** * Creates a new org * @param {import('@playwright/test').APIRequestContext} request * @returns {Promise} */ -export async function createOrgHttp(request) { - const org = await request.post(`/v1/scopes`, { +export async function createOrg(request) { + const response = await request.post(`/v1/scopes`, { data: { - name: 'Org ' + nanoid(), + name: `Org-${nanoid()}`, scope_id: 'global', }, }); - return await org.json(); + return checkResponse(response); +} + +/** + * + * @param {import('@playwright/test').APIRequestContext} request + * @param {string} orgId + * @returns {Promise} + */ +export async function deleteOrg(request, orgId) { + const response = await request.delete(`/v1/scopes/${orgId}`); + + return checkResponse(response, true); } /** @@ -26,12 +39,12 @@ export async function createOrgHttp(request) { * @param {string} scopeId ID of the scope where target will be created * @returns {Promise} */ -export async function createProjectHttp(request, scopeId) { - const project = await request.post(`/v1/scopes`, { +export async function createProject(request, scopeId) { + const response = await request.post(`/v1/scopes`, { data: { - name: 'Project ' + nanoid(), + name: `Project-${nanoid()}`, scope_id: scopeId, }, }); - return await project.json(); + return checkResponse(response); } diff --git a/e2e-tests/helpers/boundary-http/targets.js b/e2e-tests/helpers/boundary-http/targets.js index d5469337d1..b13d41e0b8 100644 --- a/e2e-tests/helpers/boundary-http/targets.js +++ b/e2e-tests/helpers/boundary-http/targets.js @@ -4,6 +4,7 @@ */ import { nanoid } from 'nanoid'; +import { checkResponse } from './responseHelper.js'; /** * Creates a new target @@ -11,19 +12,90 @@ import { nanoid } from 'nanoid'; * @param {string} scopeId ID of the scope where target will be created * @param {string} type type of the target: 'tcp, 'ssh' * @param {number} port default port of the target + * @param {string} address Optional target address * @returns {Promise} */ -export async function createTargetHttp(request, scopeId, type, port) { - const target = await request.post(`/v1/targets`, { +export async function createTarget(request, { scopeId, type, port, address }) { + const response = await request.post(`/v1/targets`, { data: { - name: 'Target ' + nanoid(), + name: `Target-${nanoid()}`, scope_id: scopeId, type: type, attributes: { default_port: port, }, + address, }, }); - return await target.json(); + return checkResponse(response); +} + +/** + * Adds a brokered credential to a target + * @param request + * @param {Object} target The target to attach the credential to + * @param {string[]} credentialIds The credential IDs to attach to the target + * @return {Promise} + */ +export async function addBrokeredCredentials( + request, + { target, credentialIds }, +) { + const response = await request.post( + `/v1/targets/${target.id}:add-credential-sources`, + { + data: { + brokered_credential_source_ids: credentialIds, + version: target.version, + }, + }, + ); + + return checkResponse(response); +} + +/** + * Adds an injected credential to a target + * @param request + * @param {Object} target The target to attach the credential to + * @param {string[]} credentialIds The credential IDs to attach to the target + * @return {Promise} + */ +export async function addInjectedCredentials( + request, + { target, credentialIds }, +) { + const response = await request.post( + `/v1/targets/${target.id}:add-credential-sources`, + { + data: { + injected_application_credential_source_ids: credentialIds, + version: target.version, + }, + }, + ); + + return checkResponse(response); +} + +/** + * Adds a host source to a target + * @param request + * @param {Object} target The target to attach the host source to + * @param {string[]} hostSourceIds The host source IDs to attach to the target + * @return {Promise} + */ +export async function addHostSource(request, { target, hostSourceIds }) { + const response = await request.post( + `/v1/targets/${target.id}:add-host-sources`, + { + data: { + host_source_ids: hostSourceIds, + version: target.version, + }, + }, + ); + + return checkResponse(response); } diff --git a/e2e-tests/helpers/vault-cli.js b/e2e-tests/helpers/vault-cli.js index bc510d965f..7c6fbd5635 100644 --- a/e2e-tests/helpers/vault-cli.js +++ b/e2e-tests/helpers/vault-cli.js @@ -50,3 +50,116 @@ export async function getVaultToken(boundaryPolicyName, secretPolicyName) { return clientToken; } + +/** + * Sets up vault oidc provider + * @param {string} vaultAddr address of vault server + * @param {string} userName name of user to create in OIDC server + * @param {string} password password of user + * @param {string} email email of user + * @param {string} boundaryAddr address of boundary server for the redirect uri + * @returns {Promise} OIDC info + */ +export async function setupVaultOidc( + vaultAddr, + userName, + password, + email, + boundaryAddr, +) { + const authPolicyName = 'auth-policy'; + + execSync(`vault auth enable userpass`); + execSync( + `vault policy write ${authPolicyName} ./admin/tests/fixtures/auth-policy.hcl`, + ); + execSync( + `vault write auth/userpass/users/${userName} password=${password} token_policies=${authPolicyName} token_ttl=1h`, + ); + execSync( + `vault write identity/entity name=${userName} metadata=email=${email} metadata=phone_number=123-456-7890 disabled=false`, + ); + const entityId = execSync( + `vault read -field=id identity/entity/name/${userName}`, + ) + .toString() + .trim(); + const groupName = 'engineering'; + execSync( + `vault write identity/group name=${groupName} member_entity_ids=${entityId}`, + ); + const groupId = execSync( + `vault read -field=id identity/group/name/${groupName}`, + ) + .toString() + .trim(); + const authList = JSON.parse( + execSync(`vault auth list -detailed -format json`), + ); + const userpassAccessor = authList['userpass/'].accessor; + execSync( + `vault write identity/entity-alias name=${userName} canonical_id=${entityId} mount_accessor=${userpassAccessor}`, + ); + const assignmentName = 'my-assignment'; + execSync( + `vault write identity/oidc/assignment/${assignmentName} entity_ids=${entityId} group_ids=${groupId}`, + ); + const keyName = 'my-key'; + execSync( + `vault write identity/oidc/key/${keyName} allowed_client_ids=* verification_ttl=2h rotation_period=1h algorithm=RS256`, + ); + const oidcClientName = 'boundary'; + execSync( + `vault write identity/oidc/client/${oidcClientName}` + + ` redirect_uris=${boundaryAddr}/v1/auth-methods/oidc:authenticate:callback` + + ` assignments=${assignmentName}` + + ` key=${keyName}` + + ` id_token_ttl=30m` + + ` access_token_ttl=1h`, + ); + const clientId = execSync( + `vault read -field=client_id identity/oidc/client/${oidcClientName}`, + ) + .toString() + .trim(); + const userScopeTemplate = ` + { + "username": {{identity.entity.name}}, + "email": {{identity.entity.metadata.email}}, + "phone_number": {{identity.entity.metadata.phone_number}} + }`; + const userScopeEncoded = Buffer.from(userScopeTemplate).toString('base64'); + execSync(`vault write identity/oidc/scope/user template=${userScopeEncoded}`); + const groupScopeTemplate = ` + { + "groups": {{identity.entity.groups.names}} + }`; + const groupScopeEncoded = Buffer.from(groupScopeTemplate).toString('base64'); + execSync( + `vault write identity/oidc/scope/groups template=${groupScopeEncoded}`, + ); + + const providerName = 'my-provider'; + execSync( + `vault write identity/oidc/provider/${providerName} allowed_client_ids=${clientId} scopes_supported=groups,user issuer=${vaultAddr} &> /dev/null`, + ); + + const oidcConfig = JSON.parse( + execSync( + `curl -s ${vaultAddr}/v1/identity/oidc/provider/${providerName}/.well-known/openid-configuration`, + ), + ); + const issuer = oidcConfig.issuer; + const clientSecret = execSync( + `vault read -field=client_secret identity/oidc/client/${oidcClientName}`, + ) + .toString() + .trim(); + + return { + issuer: issuer, + clientId: clientId, + clientSecret: clientSecret, + authPolicyName: authPolicyName, + }; +} diff --git a/e2e-tests/package.json b/e2e-tests/package.json index 224c5c5c46..7f00135ee3 100644 --- a/e2e-tests/package.json +++ b/e2e-tests/package.json @@ -7,6 +7,7 @@ "private": true, "type": "module", "scripts": { + "doc:toc": "doctoc README.md", "lint": "concurrently \"npm:lint:*(!fix)\" --names \"lint:\"", "lint:fix": "concurrently \"npm:lint:*:fix\" --names \"fix:\"", "lint:js": "eslint .", @@ -27,16 +28,17 @@ "devDependencies": { "@babel/core": "^7.25.2", "@babel/eslint-parser": "^7.21.3", - "@playwright/test": "^1.43.0", + "@playwright/test": "^1.49.0", "concurrently": "^9.1.0", "dotenv": "^16.4.5", "eslint": "^8.54.0", "eslint-config-prettier": "^9.0.0", "eslint-plugin-n": "^16.3.1", + "nanoid": "^5.0.9", "prettier": "^3.0.0" }, "engines": { - "node": "18.* || 20.*" + "node": "20.* || 22.*" }, "lint-staged": { "*.js": "eslint --fix", diff --git a/package.json b/package.json index a733f3fbd0..5145c58eda 100755 --- a/package.json +++ b/package.json @@ -20,7 +20,6 @@ "build:ui:admin:enterprise": "yarn --cwd ui/admin build:enterprise", "build:ui:admin:hcp": "yarn --cwd ui/admin build:hcp", "build:ui:desktop": "yarn --cwd ui/desktop build", - "build:ui:desktop:win": "docker-compose -f docker-compose-embedding.yml run build-desktop-production && yarn build:ui:desktop:app", "build:ui:desktop:production": "yarn --cwd ui/desktop build:production", "build:ui:desktop:app": "yarn --cwd ui/desktop build:desktop", "lint": "concurrently \"yarn:lint:*\" --names \"lint:\"", @@ -47,7 +46,7 @@ "git-cz": "^4.9.0", "husky": "^9.1.6", "license-checker": "^25.0.1", - "lint-staged": "^13.0.2" + "lint-staged": "^15.2.10" }, "resolutions": { "**/nomnom/underscore": "^1.12.1", @@ -64,5 +63,6 @@ }, "engines": { "node": "20.* || 22.*" - } + }, + "packageManager": "yarn@1.22.19+sha1.4ba7fc5c6e704fce2066ecbfb0b0d8976fe62447" } diff --git a/ui/admin/app/components/credential-stores/credential-store/actions/index.hbs b/ui/admin/app/components/credential-stores/credential-store/actions/index.hbs index f205e1110a..df3864a167 100644 --- a/ui/admin/app/components/credential-stores/credential-store/actions/index.hbs +++ b/ui/admin/app/components/credential-stores/credential-store/actions/index.hbs @@ -18,6 +18,13 @@ {{t 'resources.credential.actions.create'}} {{/if}} + {{#if (and (feature-flag 'vault-worker-filter') @model.isVault)}} + + {{t 'actions.edit-worker-filter'}} + + {{/if}} {{#if (can 'delete model' @model)}} diff --git a/ui/admin/app/components/credential-stores/credential-store/nav/index.hbs b/ui/admin/app/components/credential-stores/credential-store/nav/index.hbs index 828895fabd..0f3142cfc1 100644 --- a/ui/admin/app/components/credential-stores/credential-store/nav/index.hbs +++ b/ui/admin/app/components/credential-stores/credential-store/nav/index.hbs @@ -8,6 +8,13 @@ {{t 'titles.details'}} {{#if @model.isVault}} + {{#if (feature-flag 'vault-worker-filter')}} + + {{t 'form.worker_filter.label'}} + + {{/if}} {{#if (can 'navigate model' @model collection='credential-libraries')}} - + {{t 'resources.target.form.type.label'}} {{t 'resources.alias.form.type.label'}} diff --git a/ui/admin/app/components/form/credential-library/vault-generic/index.hbs b/ui/admin/app/components/form/credential-library/vault-generic/index.hbs index cf6e8f5aa4..b347fbee3c 100644 --- a/ui/admin/app/components/form/credential-library/vault-generic/index.hbs +++ b/ui/admin/app/components/form/credential-library/vault-generic/index.hbs @@ -24,7 +24,7 @@ {{#if @model.errors.name}} {{#each @model.errors.name as |error|}} - + {{error.message}} {{/each}} @@ -52,7 +52,11 @@ {{#if (and @model.isNew (feature-flag 'ssh-target'))}} - + {{t 'form.type.label'}} {{#each this.types as |type|}} {{#each @model.errors.path as |error|}} - {{error.message}} + {{error.message}} {{/each}} {{/if}} diff --git a/ui/admin/app/components/form/credential-library/vault-ssh-certificate/index.hbs b/ui/admin/app/components/form/credential-library/vault-ssh-certificate/index.hbs index 5236b1c347..2008d19c00 100644 --- a/ui/admin/app/components/form/credential-library/vault-ssh-certificate/index.hbs +++ b/ui/admin/app/components/form/credential-library/vault-ssh-certificate/index.hbs @@ -26,7 +26,7 @@ {{#if @model.errors.name}} {{#each @model.errors.name as |error|}} - + {{error.message}} {{/each}} @@ -54,7 +54,11 @@ {{#if @model.isNew}} - + {{t 'form.type.label'}} {{#each this.types as |type|}} {{#each @model.errors.path as |error|}} - {{error.message}} + {{error.message}} {{/each}} {{/if}} diff --git a/ui/admin/app/components/form/credential-store/static/index.hbs b/ui/admin/app/components/form/credential-store/static/index.hbs index acfda2ccd1..bcf21116d4 100644 --- a/ui/admin/app/components/form/credential-store/static/index.hbs +++ b/ui/admin/app/components/form/credential-store/static/index.hbs @@ -54,11 +54,14 @@ {{#if (feature-flag 'static-credentials')}} {{#if @model.isNew}} - + {{t 'form.type.label'}} {{#each-in this.mapResourceTypeWithIcon as |credentialStoreType icon|}} + {{t 'form.type.label'}} {{#each-in this.mapResourceTypeWithIcon as |credentialStoreType icon|}} {{#each @model.errors.address as |error|}} - {{error.message}} + {{error.message}} {{/each}} {{/if}} - {{#if (feature-flag 'vault-worker-filter')}} - - - {{t 'resources.credential-store.form.worker_filter.label'}} - - - {{t 'resources.credential-store.form.worker_filter.help'}} - - {{t 'actions.learn-more'}} - - - {{#if @model.errors.worker_filter}} - - {{#each @model.errors.worker_filter as |error|}} - {{error.message}} - {{/each}} - - {{/if}} - - {{/if}} - {{#if (or @model.isNew form.isEditable)}} type !== 'json'); + ? TYPES_CREDENTIAL + : TYPES_CREDENTIAL.filter((type) => type !== 'json'); } } diff --git a/ui/admin/app/components/form/credential/json/index.hbs b/ui/admin/app/components/form/credential/json/index.hbs index 2ded53a1d0..f37f0b6f8f 100644 --- a/ui/admin/app/components/form/credential/json/index.hbs +++ b/ui/admin/app/components/form/credential/json/index.hbs @@ -53,11 +53,10 @@ {{#if @model.isNew}} - + {{t 'form.type.label'}} {{#each @types as |credentialType|}} {{#if @model.isNew}} - + {{t 'form.type.label'}} {{#each @types as |credentialType|}} {{#if @model.isNew}} - + {{t 'form.type.label'}} {{#each @types as |credentialType|}} diff --git a/ui/admin/app/components/form/field/list-wrapper/key-value/text.hbs b/ui/admin/app/components/form/field/list-wrapper/key-value/text.hbs index 28718f97e7..df6ea5be74 100644 --- a/ui/admin/app/components/form/field/list-wrapper/key-value/text.hbs +++ b/ui/admin/app/components/form/field/list-wrapper/key-value/text.hbs @@ -6,6 +6,6 @@ \ No newline at end of file diff --git a/ui/admin/app/components/form/group/add-members/index.hbs b/ui/admin/app/components/form/group/add-members/index.hbs index 1b7fed6fac..8c9cf9cdf2 100644 --- a/ui/admin/app/components/form/group/add-members/index.hbs +++ b/ui/admin/app/components/form/group/add-members/index.hbs @@ -54,17 +54,16 @@ {{#unless this.availableUsers}} - - - {{t 'resources.group.messages.no-members.description'}} - - - - {{t 'actions.back'}} - - + + + + + + + {{/unless}} \ No newline at end of file diff --git a/ui/admin/app/components/form/host-catalog/aws/index.hbs b/ui/admin/app/components/form/host-catalog/aws/index.hbs index f5c304e60b..5f0601c685 100644 --- a/ui/admin/app/components/form/host-catalog/aws/index.hbs +++ b/ui/admin/app/components/form/host-catalog/aws/index.hbs @@ -62,11 +62,11 @@ {{#if @model.isNew}} - + {{t 'form.type.label'}} - {{#each this.hostCatalogTypes as |hostCatalogType|}} + {{#each @hostCatalogTypes as |hostCatalogType|}} {{/each}} - + {{t 'titles.provider'}} @@ -94,7 +98,7 @@ {{t 'descriptions.choose-a-provider'}} - {{#each-in this.mapResourceTypeWithIcon as |pluginType icon|}} + {{#each-in @mapResourceTypeWithIcon as |pluginType icon|}} {{/if}} - {{#if (feature-flag 'dynamic-credentials-worker-filter')}} + {{#if (feature-flag 'host-catalog-worker-filter')}} - - {{t 'resources.host-catalog.form.worker_filter.label'}} - + {{t 'form.worker_filter.label'}} {{t 'resources.host-catalog.form.worker_filter.help'}} @@ -396,7 +397,7 @@ name='disable_credential_rotation' checked={{@model.disable_credential_rotation}} disabled={{form.disabled}} - {{on 'change' (fn this.toggleDisableCredentialRotation @model)}} + {{on 'change' (fn @toggleDisableCredentialRotation @model)}} as |F| > diff --git a/ui/admin/app/components/form/host-catalog/aws/index.js b/ui/admin/app/components/form/host-catalog/aws/index.js index af42090d05..1385ea0ee4 100644 --- a/ui/admin/app/components/form/host-catalog/aws/index.js +++ b/ui/admin/app/components/form/host-catalog/aws/index.js @@ -6,20 +6,12 @@ import Component from '@glimmer/component'; import { action } from '@ember/object'; import { - TYPES_HOST_CATALOG, - TYPES_HOST_CATALOG_PLUGIN, TYPES_CREDENTIALS, TYPE_CREDENTIAL_DYNAMIC, } from 'api/models/host-catalog'; import { tracked } from '@glimmer/tracking'; -//Note: this is a temporary solution till we have resource type helper in place -const icons = ['aws-color', 'azure-color']; - export default class FormHostCatalogAwsComponent extends Component { - // =properties - hostCatalogTypes = TYPES_HOST_CATALOG; - // =attributes @tracked selectedCredentialType = this.args.model.credentialType; @@ -34,23 +26,6 @@ export default class FormHostCatalogAwsComponent extends Component { return TYPES_CREDENTIALS; } - /** - * maps resource type with icon - * @type {object} - */ - get mapResourceTypeWithIcon() { - return TYPES_HOST_CATALOG_PLUGIN.reduce( - (obj, plugin, i) => ({ ...obj, [plugin]: icons[i] }), - {}, - ); - } - // =actions - - @action - toggleDisableCredentialRotation(model) { - model.disable_credential_rotation = !model.disable_credential_rotation; - } - /** * Allows to update the credential type * @param type {string} diff --git a/ui/admin/app/components/form/host-catalog/azure/index.hbs b/ui/admin/app/components/form/host-catalog/azure/index.hbs index 9e8fa239b0..fc50330059 100644 --- a/ui/admin/app/components/form/host-catalog/azure/index.hbs +++ b/ui/admin/app/components/form/host-catalog/azure/index.hbs @@ -50,9 +50,9 @@ {{#if @model.isNew}} - + {{t 'form.type.label'}} - {{#each this.hostCatalogTypes as |hostCatalogType|}} + {{#each @hostCatalogTypes as |hostCatalogType|}} {{/each}} - + {{t 'titles.provider'}} {{t 'titles.choose-a-provider'}} {{concat (t 'descriptions.choose-a-provider') }} - {{#each-in this.mapResourceTypeWithIcon as |pluginType icon|}} + {{#each-in @mapResourceTypeWithIcon as |pluginType icon|}} + + {{t 'form.name.label'}} + {{t 'form.name.help'}} + {{#if @model.errors.name}} + + {{#each @model.errors.name as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + {{t 'form.description.label'}} + {{t 'form.description.help'}} + {{#if @model.errors.description}} + + {{#each @model.errors.description as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + {{#if @model.isNew}} + + {{t 'form.type.label'}} + {{#each @hostCatalogTypes as |hostCatalogType|}} + + {{t + (concat 'resources.host-catalog.types.' hostCatalogType) + }} + {{t + (concat 'resources.host-catalog.help.' hostCatalogType) + }} + + {{/each}} + + + {{t 'titles.provider'}} + {{t 'titles.choose-a-provider'}} + {{concat + (t 'descriptions.choose-a-provider') + }} + + {{#each-in @mapResourceTypeWithIcon as |pluginType icon|}} + + {{t + (concat 'resources.host-catalog.types.' pluginType) + }} + + + {{/each-in}} + + {{else}} + + {{t 'titles.provider'}} + {{t 'descriptions.provider'}} + + + {{/if}} + + + {{t 'resources.host-catalog.form.project_id.label'}} + {{t + 'resources.host-catalog.form.project_id.help' + }} + {{#if @model.errors.project_id}} + + {{#each @model.errors.project_id as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + {{t 'resources.host-catalog.form.zone.label'}} + {{t 'resources.host-catalog.form.zone.help'}} + {{#if @model.errors.zone}} + + {{#each @model.errors.zone as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + {{t 'resources.host-catalog.form.client_email.label'}} + {{t + 'resources.host-catalog.form.client_email.help' + }} + {{#if @model.errors.client_email}} + + {{#each @model.errors.client_email as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + {{t + 'resources.host-catalog.form.target_service_account_id.label' + }} + {{t + 'resources.host-catalog.form.target_service_account_id.help' + }} + {{#if @model.errors.target_service_account_id}} + + {{#each @model.errors.target_service_account_id as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + {{t 'resources.host-catalog.form.private_key_id.label'}} + {{t + 'resources.host-catalog.form.private_key_id.help' + }} + {{#if @model.errors.private_key_id}} + + {{#each @model.errors.private_key_id as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + {{t 'resources.host-catalog.form.private_key.label'}} + {{t + 'resources.host-catalog.form.private_key.help' + }} + {{#if @model.errors.private_key}} + + {{#each @model.errors.private_key as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + {{#if (feature-flag 'host-catalog-worker-filter')}} + + {{t 'form.worker_filter.label'}} + + {{t 'resources.host-catalog.form.worker_filter.help'}} + + {{t 'actions.learn-more'}} + + + {{#if @model.errors.worker_filter}} + + {{#each @model.errors.worker_filter as |error|}} + + {{error.message}} + + {{/each}} + + {{/if}} + + {{/if}} + + + {{t 'form.disable_credential_rotation.label'}} + + + {{#if (can 'save model' @model)}} + + {{/if}} + + \ No newline at end of file diff --git a/ui/admin/app/components/form/host-catalog/index.hbs b/ui/admin/app/components/form/host-catalog/index.hbs index 49a01af82c..f65e9fdaef 100644 --- a/ui/admin/app/components/form/host-catalog/index.hbs +++ b/ui/admin/app/components/form/host-catalog/index.hbs @@ -10,4 +10,7 @@ cancel=@cancel changeType=@changeType changeCredentialType=@changeCredentialType + mapResourceTypeWithIcon=this.mapResourceTypeWithIcon + hostCatalogTypes=this.hostCatalogTypes + toggleDisableCredentialRotation=this.toggleDisableCredentialRotation }} \ No newline at end of file diff --git a/ui/admin/app/components/form/host-catalog/azure/index.js b/ui/admin/app/components/form/host-catalog/index.js similarity index 61% rename from ui/admin/app/components/form/host-catalog/azure/index.js rename to ui/admin/app/components/form/host-catalog/index.js index c5598e620d..b7a7232db7 100644 --- a/ui/admin/app/components/form/host-catalog/azure/index.js +++ b/ui/admin/app/components/form/host-catalog/index.js @@ -4,15 +4,16 @@ */ import Component from '@glimmer/component'; +import { action } from '@ember/object'; + import { TYPES_HOST_CATALOG, TYPES_HOST_CATALOG_PLUGIN, } from 'api/models/host-catalog'; -//Note: this is a temporary solution till we have resource type helper in place -const icons = ['aws-color', 'azure-color']; +const icons = ['aws-color', 'azure-color', 'gcp-color']; -export default class FormAzureHostCatalogAwsComponent extends Component { +export default class FormHostCatalogIndexComponent extends Component { // =properties hostCatalogTypes = TYPES_HOST_CATALOG; /** @@ -25,4 +26,11 @@ export default class FormAzureHostCatalogAwsComponent extends Component { {}, ); } + + // =actions + + @action + toggleDisableCredentialRotation(model) { + model.disable_credential_rotation = !model.disable_credential_rotation; + } } diff --git a/ui/admin/app/components/form/host-catalog/static/index.hbs b/ui/admin/app/components/form/host-catalog/static/index.hbs index 4f28b4ae29..70dd89bc03 100644 --- a/ui/admin/app/components/form/host-catalog/static/index.hbs +++ b/ui/admin/app/components/form/host-catalog/static/index.hbs @@ -51,9 +51,9 @@ {{#if @model.isNew}} - + {{t 'form.type.label'}} - {{#each this.hostCatalogTypes as |hostCatalogType|}} + {{#each @hostCatalogTypes as |hostCatalogType|}} ({ ...obj, [plugin]: icons[i] }), - {}, - ); - } -} diff --git a/ui/admin/app/components/form/host-set/add-hosts/index.hbs b/ui/admin/app/components/form/host-set/add-hosts/index.hbs index 84575d6c32..c65f5f51a4 100644 --- a/ui/admin/app/components/form/host-set/add-hosts/index.hbs +++ b/ui/admin/app/components/form/host-set/add-hosts/index.hbs @@ -47,19 +47,20 @@ {{#unless this.hasAvailableHosts}} - - - {{t 'resources.host-set.host.messages.add-none.description'}} - - - - {{t 'actions.back'}} - - + + + + + + + {{/unless}} \ No newline at end of file diff --git a/ui/admin/app/components/form/host-set/aws/index.hbs b/ui/admin/app/components/form/host-set/aws/index.hbs index 3ead450617..8eb10dfb20 100644 --- a/ui/admin/app/components/form/host-set/aws/index.hbs +++ b/ui/admin/app/components/form/host-set/aws/index.hbs @@ -51,16 +51,11 @@ {{/if}} - + {{t 'form.type.label'}} - + {{t 'titles.provider'}} {{t 'resources.host-catalog.types.aws'}} diff --git a/ui/admin/app/components/form/host-set/azure/index.hbs b/ui/admin/app/components/form/host-set/azure/index.hbs index 1873a32354..24f9338d7e 100644 --- a/ui/admin/app/components/form/host-set/azure/index.hbs +++ b/ui/admin/app/components/form/host-set/azure/index.hbs @@ -51,16 +51,11 @@ {{/if}} - + {{t 'form.type.label'}} - + {{t 'titles.provider'}} {{t 'resources.host-catalog.types.azure'}} diff --git a/ui/admin/app/components/form/host-set/gcp/index.hbs b/ui/admin/app/components/form/host-set/gcp/index.hbs new file mode 100644 index 0000000000..23a0e5bf23 --- /dev/null +++ b/ui/admin/app/components/form/host-set/gcp/index.hbs @@ -0,0 +1,156 @@ +{{! + Copyright (c) HashiCorp, Inc. + SPDX-License-Identifier: BUSL-1.1 +}} + + + + {{t 'form.name.label'}} + {{t 'form.name.help'}} + {{#if @model.errors.name}} + + {{#each @model.errors.name as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + {{t 'form.description.label'}} + {{t 'form.description.help'}} + {{#if @model.errors.description}} + + {{#each @model.errors.description as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + {{t 'form.type.label'}} + + + + {{t 'titles.provider'}} + + {{t 'resources.host-catalog.types.gcp'}} + + + + + <:fieldset as |F|> + + {{t 'resources.host-set.form.preferred_endpoints.label'}} + + + {{t 'resources.host-set.form.preferred_endpoints.help'}} + + + {{#if @model.errors.preferred_endpoints}} + + {{#each @model.errors.preferred_endpoints as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + <:field as |F|> + + + + + + <:fieldset as |F|> + + {{t 'resources.host-set.form.filter.label'}} + + + {{t 'resources.host-set.form.filter.gcp.help'}} + + + {{#if @model.errors.filters}} + + {{#each @model.errors.filters as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + <:field as |F|> + + + + + + {{t 'form.sync-interval.label'}} + + {{t 'form.sync-interval.help'}} + + {{t 'actions.learn-more'}} + + + {{#if @model.errors.sync_interval_seconds}} + + {{#each @model.errors.sync_interval_seconds as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + {{#if (can 'save model' @model)}} + + {{/if}} + \ No newline at end of file diff --git a/ui/admin/app/components/form/host/aws/index.hbs b/ui/admin/app/components/form/host/aws/index.hbs index d89b87a616..810d436245 100644 --- a/ui/admin/app/components/form/host/aws/index.hbs +++ b/ui/admin/app/components/form/host/aws/index.hbs @@ -30,13 +30,6 @@ > {{t 'form.external_name.label'}} {{t 'form.external_name.help'}} - {{#if @model.errors.external_name}} - - {{#each @model.errors.external_name as |error|}} - {{error.message}} - {{/each}} - - {{/if}} {{/if}} @@ -49,13 +42,6 @@ > {{t 'form.external_id.label'}} {{t 'form.external_id.help'}} - {{#if @model.errors.external_id}} - - {{#each @model.errors.external_id as |error|}} - {{error.message}} - {{/each}} - - {{/if}} {{t 'form.description.label'}} {{t 'form.description.help'}} - {{#if @model.errors.description}} - - {{#each @model.errors.description as |error|}} - {{error.message}} - {{/each}} - - {{/if}} - + {{t 'form.ip_addresses.label'}} {{t 'form.ip_addresses.help'}} {{#each @model.ip_addresses as |ip_address|}} @@ -102,7 +81,7 @@ {{/each}} - + {{t 'form.dns_names.label'}} {{t 'form.dns_names.help'}} {{#each @model.dns_names as |dns_name|}} diff --git a/ui/admin/app/components/form/host/azure/index.hbs b/ui/admin/app/components/form/host/azure/index.hbs index 5133350fc5..9ec27094f6 100644 --- a/ui/admin/app/components/form/host/azure/index.hbs +++ b/ui/admin/app/components/form/host/azure/index.hbs @@ -30,13 +30,6 @@ > {{t 'form.external_name.label'}} {{t 'form.external_name.help'}} - {{#if @model.errors.external_name}} - - {{#each @model.errors.external_name as |error|}} - {{error.message}} - {{/each}} - - {{/if}} {{/if}} @@ -49,13 +42,6 @@ > {{t 'form.external_id.label'}} {{t 'form.external_id.help'}} - {{#if @model.errors.external_id}} - - {{#each @model.errors.external_id as |error|}} - {{error.message}} - {{/each}} - - {{/if}} {{t 'form.description.label'}} {{t 'form.description.help'}} - {{#if @model.errors.description}} - - {{#each @model.errors.description as |error|}} - {{error.message}} - {{/each}} - - {{/if}} - + {{t 'form.ip_addresses.label'}} {{t 'form.ip_addresses.help'}} {{#each @model.ip_addresses as |ip_address|}} diff --git a/ui/admin/app/components/form/host/gcp/index.hbs b/ui/admin/app/components/form/host/gcp/index.hbs new file mode 100644 index 0000000000..59facc4c96 --- /dev/null +++ b/ui/admin/app/components/form/host/gcp/index.hbs @@ -0,0 +1,91 @@ +{{! + Copyright (c) HashiCorp, Inc. + SPDX-License-Identifier: BUSL-1.1 +}} + + + + + {{t 'form.type.label'}} + + + {{#if @model.external_name}} + + {{t 'form.external_name.label'}} + {{t 'form.external_name.help'}} + + {{/if}} + + + {{t 'form.external_id.label'}} + {{t 'form.external_id.help'}} + + + + {{t 'form.description.label'}} + {{t 'form.description.help'}} + + + + {{t 'titles.provider'}} + + {{t 'resources.host-catalog.types.gcp'}} + + + + + {{t 'form.ip_addresses.label'}} + {{t 'form.ip_addresses.help'}} + {{#each @model.ip_addresses as |ip_address|}} + + + + {{/each}} + + + {{#if (can 'save model' @model)}} + + {{/if}} + \ No newline at end of file diff --git a/ui/admin/app/components/form/host/static/index.hbs b/ui/admin/app/components/form/host/static/index.hbs index c5cfd9dbb6..fa3e1c6144 100644 --- a/ui/admin/app/components/form/host/static/index.hbs +++ b/ui/admin/app/components/form/host/static/index.hbs @@ -68,7 +68,9 @@ {{#if @model.errors.address}} {{#each @model.errors.address as |error|}} - {{error.message}} + {{error.message}} {{/each}} {{/if}} diff --git a/ui/admin/app/components/form/role/add-principals/index.hbs b/ui/admin/app/components/form/role/add-principals/index.hbs index 74c347afd2..8cd43f9878 100644 --- a/ui/admin/app/components/form/role/add-principals/index.hbs +++ b/ui/admin/app/components/form/role/add-principals/index.hbs @@ -56,17 +56,18 @@ {{#unless this.hasAvailablePrincipals}} - - - {{t 'resources.role.principal.messages.none.description'}} - - - - {{t 'actions.back'}} - - + + + + + + + {{/unless}} \ No newline at end of file diff --git a/ui/admin/app/components/form/role/index.hbs b/ui/admin/app/components/form/role/index.hbs index 144be81f8f..b5af5bd80e 100644 --- a/ui/admin/app/components/form/role/index.hbs +++ b/ui/admin/app/components/form/role/index.hbs @@ -17,7 +17,7 @@ @isInvalid={{@model.errors.name}} @type='text' name='name' - disabled={{form.disabled}} + readonly={{form.disabled}} {{on 'input' (set-from-event @model 'name')}} as |F| > @@ -37,7 +37,7 @@ @value={{@model.description}} @isInvalid={{@model.errors.description}} name='description' - disabled={{form.disabled}} + readonly={{form.disabled}} as |F| > {{t 'form.description.label'}} diff --git a/ui/admin/app/components/form/storage-bucket/aws/index.hbs b/ui/admin/app/components/form/storage-bucket/aws/index.hbs index 4006830f63..473eca93a9 100644 --- a/ui/admin/app/components/form/storage-bucket/aws/index.hbs +++ b/ui/admin/app/components/form/storage-bucket/aws/index.hbs @@ -115,7 +115,11 @@ {{/if}} {{! Provider / plugin.name }} - + {{t 'titles.provider'}} {{t 'descriptions.choose-a-provider'}} @@ -212,11 +216,10 @@ {{! credential_type }} - + {{t 'resources.storage-bucket.types.credential'}} {{#each this.credentials as |credentialType|}} - {{t 'resources.storage-bucket.form.worker_filter.label'}} - - {{t 'resources.storage-bucket.form.worker_filter.help'}} - - {{t 'actions.learn-more'}} - - - {{#if @model.errors.worker_filter}} - - {{#each @model.errors.worker_filter as |error|}} - {{error.message}} - {{/each}} - - {{/if}} - + {{#let (unique-id) (unique-id) as |labelId helpId|}} + + {{t 'form.worker_filter.label'}} + + {{t 'resources.storage-bucket.form.worker_filter.help'}} + + {{t 'actions.learn-more'}} + + + + {{#if (and form.disabled (not @model.isSaving))}} + + {{else}} + + {{/if}} + + + {{/let}} {{! JUST for static credentials }} {{#unless this.showDynamicCredentials}} diff --git a/ui/admin/app/components/form/storage-bucket/aws/index.js b/ui/admin/app/components/form/storage-bucket/aws/index.js index 9671c701c2..3d8d43bd31 100644 --- a/ui/admin/app/components/form/storage-bucket/aws/index.js +++ b/ui/admin/app/components/form/storage-bucket/aws/index.js @@ -13,10 +13,11 @@ import { tracked } from '@glimmer/tracking'; export default class FormStorageBucketAwsIndexComponent extends Component { // =attributes - @tracked selectedCredentialType = this.args.model.credentialType; + @tracked selectedCredentialType = this.args.model.credentialType; @tracked showDynamicCredentials = this.args.model.credentialType === TYPE_CREDENTIAL_DYNAMIC; + /** * returns an array of available credential types * @type {array} @@ -28,7 +29,7 @@ export default class FormStorageBucketAwsIndexComponent extends Component { // =actions /** * Allows to update the credential type - * @param type {string} + * @param {string} type */ @action updateCredentialTypeSelection(type) { diff --git a/ui/admin/app/components/form/storage-bucket/minio/index.hbs b/ui/admin/app/components/form/storage-bucket/minio/index.hbs index 503f938dbb..ad298c3cbf 100644 --- a/ui/admin/app/components/form/storage-bucket/minio/index.hbs +++ b/ui/admin/app/components/form/storage-bucket/minio/index.hbs @@ -115,7 +115,11 @@ {{/if}} {{! Provider / plugin.name }} - + {{t 'titles.provider'}} {{t 'descriptions.choose-a-provider'}} @@ -311,30 +315,40 @@ {{/if}} {{! worker_filter }} - - {{t 'resources.storage-bucket.form.worker_filter.label'}} - - {{t 'resources.storage-bucket.form.worker_filter.help'}} - - {{t 'actions.learn-more'}} - - - {{#if @model.errors.worker_filter}} - - {{#each @model.errors.worker_filter as |error|}} - {{error.message}} - {{/each}} - - {{/if}} - + {{#let (unique-id) (unique-id) as |labelId helpId|}} + + {{t 'form.worker_filter.label'}} + + {{t 'resources.storage-bucket.form.worker_filter.help'}} + + {{t 'actions.learn-more'}} + + + + {{#if (and form.disabled (not @model.isSaving))}} + + {{else}} + + {{/if}} + + + {{/let}} {{! disable_credential_rotation }} - - - {{t 'resources.target.host-source.messages.none.description'}} - - - - {{t 'actions.back'}} - - + + + + + + + {{/unless}} \ No newline at end of file diff --git a/ui/admin/app/components/form/target/add-host-sets/index.js b/ui/admin/app/components/form/target/add-host-sets/index.js index 106df7dc3a..45b5de5197 100644 --- a/ui/admin/app/components/form/target/add-host-sets/index.js +++ b/ui/admin/app/components/form/target/add-host-sets/index.js @@ -4,7 +4,7 @@ */ import Component from '@glimmer/component'; -import { computed, action } from '@ember/object'; +import { action } from '@ember/object'; import { A } from '@ember/array'; import { inject as service } from '@ember/service'; import { loading } from 'ember-loading'; @@ -25,11 +25,10 @@ export default class FormTargetAddHostSetsComponent extends Component { selectedHostSetIDs = A(); /** - * Checks for unassigned hostsets. + * Checks for unassigned host-sets. * @param {[HostSetModel]} filteredHostSets * @type {boolean} */ - @computed('filteredHostSets.length') get hasAvailableHostSets() { return this.filteredHostSets.length > 0; } @@ -38,7 +37,6 @@ export default class FormTargetAddHostSetsComponent extends Component { * Host sets not already added to the target. * @type {[HostSetModel]} */ - @computed('args.{hostSets.[],model.host_sources.[]}') get filteredHostSets() { // Get IDs for host sets already added to the current target const alreadyAddedHostSetIDs = this.args.model.host_sources.map( diff --git a/ui/admin/app/components/form/target/details/index.hbs b/ui/admin/app/components/form/target/details/index.hbs index c9d934dc7a..2b67648821 100644 --- a/ui/admin/app/components/form/target/details/index.hbs +++ b/ui/admin/app/components/form/target/details/index.hbs @@ -53,7 +53,11 @@ {{#if (and @model.isNew (feature-flag 'ssh-target'))}} - + {{t 'resources.target.form.type.label'}} {{t 'resources.target.form.type.help'}} {{#each this.typeMetas as |type|}} diff --git a/ui/admin/app/components/form/user/add-accounts/index.hbs b/ui/admin/app/components/form/user/add-accounts/index.hbs index 4377ab0c57..9d54ff5f43 100644 --- a/ui/admin/app/components/form/user/add-accounts/index.hbs +++ b/ui/admin/app/components/form/user/add-accounts/index.hbs @@ -53,17 +53,16 @@ {{#unless this.hasAvailableAccounts}} - - - {{t 'resources.user.messages.no-accounts.description'}} - - - - {{t 'actions.back'}} - - + + + + + + + {{/unless}} \ No newline at end of file diff --git a/ui/admin/app/components/form/worker-filter/index.hbs b/ui/admin/app/components/form/worker-filter/index.hbs new file mode 100644 index 0000000000..d9cc163c12 --- /dev/null +++ b/ui/admin/app/components/form/worker-filter/index.hbs @@ -0,0 +1,21 @@ +{{! + Copyright (c) HashiCorp, Inc. + SPDX-License-Identifier: BUSL-1.1 +}} + + + + + {{#if (can 'save model' @model)}} + + {{/if}} + \ No newline at end of file diff --git a/ui/admin/app/components/host-catalog-type-badge/index.js b/ui/admin/app/components/host-catalog-type-badge/index.js index 250518327d..988e81c934 100644 --- a/ui/admin/app/components/host-catalog-type-badge/index.js +++ b/ui/admin/app/components/host-catalog-type-badge/index.js @@ -4,8 +4,7 @@ */ import Component from '@glimmer/component'; -const types = ['aws', 'azure']; - +import { TYPES_HOST_CATALOG_PLUGIN } from 'api/models/host-catalog'; export default class HostCatalogTypeComponent extends Component { /** * Display icons only for plugin compositeTypes. @@ -13,7 +12,7 @@ export default class HostCatalogTypeComponent extends Component { */ get icon() { return ( - types.includes(this.args.model.compositeType) && + TYPES_HOST_CATALOG_PLUGIN.includes(this.args.model.compositeType) && `${this.args.model.compositeType}-color` ); } diff --git a/ui/admin/app/components/targets/target/nav/index.hbs b/ui/admin/app/components/targets/target/nav/index.hbs index 6f5ed5e3e4..c0f88501a3 100644 --- a/ui/admin/app/components/targets/target/nav/index.hbs +++ b/ui/admin/app/components/targets/target/nav/index.hbs @@ -8,7 +8,7 @@ {{t 'titles.details'}} - {{t 'resources.target.workers.title'}} + {{t 'titles.workers'}} {{t 'resources.target.host-source.title_plural'}} diff --git a/ui/admin/app/components/worker-filter-generator/index.hbs b/ui/admin/app/components/worker-filter-generator/index.hbs new file mode 100644 index 0000000000..761e697993 --- /dev/null +++ b/ui/admin/app/components/worker-filter-generator/index.hbs @@ -0,0 +1,186 @@ +{{! + Copyright (c) HashiCorp, Inc. + SPDX-License-Identifier: BUSL-1.1 +}} + + + + + {{#unless @hideToolbar}} + + {{/unless}} + + + + {{#if (get @model.errors @name)}} + + {{#each (get @model.errors @name) as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + + + {{t 'worker-filter-generator.toggle.title'}} + {{t + 'worker-filter-generator.toggle.description' + }} + + + + +{{#if this.showFilterGenerator}} + + {{t 'worker-filter-generator.title'}} + + {{t 'worker-filter-generator.description'}} +
+ + {{t 'worker-filter-generator.link'}} + +
+ + {{t 'worker-filter-generator.tag.label'}} + {{t 'worker-filter-generator.tag.helper'}} + + + {{t 'worker-filter-generator.name.label'}} + {{t 'worker-filter-generator.name.helper'}} + +
+ + {{#let (unique-id) (unique-id) as |labelId helpId|}} + + {{t + 'worker-filter-generator.input-values.title' + }} + {{t + 'worker-filter-generator.input-values.description' + }} + + {{#if (eq this.selectedGeneratorType this.generatorTagType)}} + + {{t 'form.key.label'}} + + {{else}} + + {{t 'form.operator.label'}} + + + {{#each this.operatorOptions as |operator|}} + + {{/each}} + + + {{/if}} + + + + {{t 'form.value.label'}} + + + + {{/let}} + + {{#let (unique-id) (unique-id) as |labelId helpId|}} + + {{t + 'worker-filter-generator.formatted-result.title' + }} + {{t + 'worker-filter-generator.formatted-result.description' + }} + + {{t + 'worker-filter-generator.formatted-result.label' + }} +
+ + {{#if this.generatedResult}} + + {{/if}} +
+
+
+ {{/let}} +{{/if}} \ No newline at end of file diff --git a/ui/admin/app/components/worker-filter-generator/index.js b/ui/admin/app/components/worker-filter-generator/index.js new file mode 100644 index 0000000000..470903b140 --- /dev/null +++ b/ui/admin/app/components/worker-filter-generator/index.js @@ -0,0 +1,70 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import Component from '@glimmer/component'; +import { action } from '@ember/object'; +import { tracked } from '@glimmer/tracking'; + +export default class WorkerFilterGeneratorIndexComponent extends Component { + // =attributes + + generatorTagType = 'tag'; + generatorNameType = 'name'; + operatorOptions = ['==', 'matches', 'contains']; + @tracked showFilterGenerator = true; + @tracked selectedGeneratorType = this.generatorTagType; + @tracked key = ''; + @tracked value = ''; + @tracked operator = ''; + + /** + * @return {string} + */ + get generatedResult() { + let valueResult = this.value ? `"${this.value}"` : ''; + if (this.selectedGeneratorType === 'tag') { + let keyResult = this.key ? `"/tags/${this.key}"` : ''; + return keyResult || valueResult ? `${valueResult} in ${keyResult}` : ''; + } else { + return valueResult || this.operator + ? `"/name" ${this.operator} ${valueResult}` + : ''; + } + } + + // =actions + + /** + * Sets the model filter to the passed in value. + * @param {Model} model + * @param {string} filter + * @param {string} value + */ + @action + setWorkerFilter(model, filter, value) { + model[filter] = value; + } + + /** + * Toggles showFilterGenerator attribute to true or false. + */ + @action + toggleFilterGenerator() { + this.showFilterGenerator = !this.showFilterGenerator; + } + + /** + * Clears out key, value, and operator. + * Sets selectedGeneratorType to a value. + * @param {object} event + */ + @action + setGeneratorType(event) { + this.key = ''; + this.value = ''; + this.operator = ''; + this.selectedGeneratorType = event.target.value; + } +} diff --git a/ui/admin/app/components/worker-filter/index.hbs b/ui/admin/app/components/worker-filter/index.hbs deleted file mode 100644 index ed65622f2f..0000000000 --- a/ui/admin/app/components/worker-filter/index.hbs +++ /dev/null @@ -1,41 +0,0 @@ -{{! - Copyright (c) HashiCorp, Inc. - SPDX-License-Identifier: BUSL-1.1 -}} - - - - - - - - - - {{#if (get @model.errors @name)}} - - {{#each (get @model.errors @name) as |error|}} - {{error.message}} - {{/each}} - - {{/if}} - - - - \ No newline at end of file diff --git a/ui/admin/app/components/worker-filter/index.js b/ui/admin/app/components/worker-filter/index.js deleted file mode 100644 index 4da609eb11..0000000000 --- a/ui/admin/app/components/worker-filter/index.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -import Component from '@glimmer/component'; -import { action } from '@ember/object'; - -export default class WorkerFilterComponent extends Component { - // =actions - - /** - * Sets the model filter to the passed in value. - * @param {Model} model - * @param {string} filter - * @param {string} value - */ - @action - setWorkerFilter(model, filter, value) { - model[filter] = value; - } -} diff --git a/ui/admin/app/controllers/scopes/scope/aliases/index.js b/ui/admin/app/controllers/scopes/scope/aliases/index.js index 15e64ac618..ef39cf83af 100644 --- a/ui/admin/app/controllers/scopes/scope/aliases/index.js +++ b/ui/admin/app/controllers/scopes/scope/aliases/index.js @@ -16,6 +16,7 @@ export default class ScopesScopeAliasesIndexController extends Controller { // =services @service can; + @service intl; @service router; // =attributes @@ -26,6 +27,31 @@ export default class ScopesScopeAliasesIndexController extends Controller { @tracked page = 1; @tracked pageSize = 10; + /** + * If can list (at least): return default welcome message. + * If can create (only): return create-but-not-list welcome message. + * If can neither list nor create: return neither-list-nor-create welcome message + * @type {string} + */ + get messageDescription() { + const canList = this.can.can('list model', this.scope, { + collection: 'aliases', + }); + const canCreate = this.can.can('create model', this.scope, { + collection: 'aliases', + }); + const resource = this.intl.t('resources.alias.title_plural'); + let description = 'descriptions.neither-list-nor-create'; + + if (canList) { + description = 'resources.alias.messages.none.description'; + } else if (canCreate) { + description = 'descriptions.create-but-not-list'; + } + + return this.intl.t(description, { resource }); + } + // =actions /** diff --git a/ui/admin/app/controllers/scopes/scope/auth-methods/auth-method/accounts/index.js b/ui/admin/app/controllers/scopes/scope/auth-methods/auth-method/accounts/index.js index 8606dcdc51..69f90fd36a 100644 --- a/ui/admin/app/controllers/scopes/scope/auth-methods/auth-method/accounts/index.js +++ b/ui/admin/app/controllers/scopes/scope/auth-methods/auth-method/accounts/index.js @@ -16,8 +16,36 @@ export default class ScopesScopeAuthMethodsAuthMethodAccountsIndexController ext // =services @service can; + @service intl; @service router; + // =attributes + + /** + * If can list (at least): return default welcome message. + * If can create (only): return create-but-not-list welcome message. + * If can neither list nor create: return neither-list-nor-create welcome message + * @type {string} + */ + get messageDescription() { + const canList = this.can.can('list model', this.authMethod, { + collection: 'accounts', + }); + const canCreate = this.can.can('create model', this.authMethod, { + collection: 'accounts', + }); + const resource = this.intl.t('resources.account.title_plural'); + let description = 'descriptions.neither-list-nor-create'; + + if (canList) { + description = 'resources.account.description'; + } else if (canCreate) { + description = 'descriptions.create-but-not-list'; + } + + return this.intl.t(description, { resource }); + } + // =actions /** diff --git a/ui/admin/app/controllers/scopes/scope/auth-methods/auth-method/managed-groups/index.js b/ui/admin/app/controllers/scopes/scope/auth-methods/auth-method/managed-groups/index.js index 4b74916099..1361bca8b4 100644 --- a/ui/admin/app/controllers/scopes/scope/auth-methods/auth-method/managed-groups/index.js +++ b/ui/admin/app/controllers/scopes/scope/auth-methods/auth-method/managed-groups/index.js @@ -16,8 +16,36 @@ export default class ScopesScopeAuthMethodsAuthMethodManagedGroupsIndexControlle // =services @service can; + @service intl; @service router; + // =attributes + + /** + * If can list (at least): return default welcome message. + * If can create (only): return create-but-not-list welcome message. + * If can neither list nor create: return neither-list-nor-create welcome message + * @type {string} + */ + get messageDescription() { + const canList = this.can.can('list model', this.authMethod, { + collection: 'managed-groups', + }); + const canCreate = this.can.can('create model', this.authMethod, { + collection: 'managed-groups', + }); + const resource = this.intl.t('resources.managed-group.title_plural'); + let description = 'descriptions.neither-list-nor-create'; + + if (canList) { + description = 'resources.managed-group.description'; + } else if (canCreate) { + description = 'descriptions.create-but-not-list'; + } + + return this.intl.t(description, { resource }); + } + // =actions /** diff --git a/ui/admin/app/controllers/scopes/scope/auth-methods/index.js b/ui/admin/app/controllers/scopes/scope/auth-methods/index.js index 4de6147f37..545cf6a6ca 100644 --- a/ui/admin/app/controllers/scopes/scope/auth-methods/index.js +++ b/ui/admin/app/controllers/scopes/scope/auth-methods/index.js @@ -72,6 +72,31 @@ export default class ScopesScopeAuthMethodsIndexController extends Controller { }; } + /** + * If can list (at least): return default welcome message. + * If can create (only): return create-but-not-list welcome message. + * If can neither list nor create: return neither-list-nor-create welcome message + * @type {string} + */ + get messageDescription() { + const canList = this.can.can('list model', this.scopeModel, { + collection: 'auth-methods', + }); + const canCreate = this.can.can('create model', this.scopeModel, { + collection: 'auth-methods', + }); + const resource = this.intl.t('resources.auth-method.title_plural'); + let description = 'descriptions.neither-list-nor-create'; + + if (canList) { + description = 'resources.auth-method.description'; + } else if (canCreate) { + description = 'descriptions.create-but-not-list'; + } + + return this.intl.t(description, { resource }); + } + // =actions /** diff --git a/ui/admin/app/controllers/scopes/scope/authenticate/method/oidc.js b/ui/admin/app/controllers/scopes/scope/authenticate/method/oidc.js index c64b230c31..ad55293dac 100644 --- a/ui/admin/app/controllers/scopes/scope/authenticate/method/oidc.js +++ b/ui/admin/app/controllers/scopes/scope/authenticate/method/oidc.js @@ -3,21 +3,34 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import Controller from '@ember/controller'; +import Controller, { inject as controller } from '@ember/controller'; import { inject as service } from '@ember/service'; +import { action } from '@ember/object'; export default class ScopesScopeAuthenticateMethodOidcController extends Controller { + @controller('scopes/scope/authenticate/method/index') authenticateMethod; + // =services @service session; - // =attributes + // =actions /** - * Authentication URL for the pending OIDC flow, if any. - * @type {?string} + * Retry by starting a new OIDC authentication flow. + * @param {object} e */ - get authURL() { - return this.session.data.pending.oidc.attributes.auth_url; + @action + async retryAuthentication(e) { + // TODO: This event handler can be removed when rose dialog gets + // refactored to use hds. + e.preventDefault(); + + const scope = this.authMethod.scope; + const authenticatorName = `authenticator:${this.authMethod.type}`; + await this.authenticateMethod.startOIDCAuthentication(authenticatorName, { + scope, + authMethod: this.authMethod, + }); } } diff --git a/ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/credential-libraries/index.js b/ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/credential-libraries/index.js index 2cd4083e23..6a1bad0b2d 100644 --- a/ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/credential-libraries/index.js +++ b/ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/credential-libraries/index.js @@ -16,8 +16,36 @@ export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrari // =services @service can; + @service intl; @service router; + // =attributes + + /** + * If can list (at least): return default welcome message. + * If can create (only): return create-but-not-list welcome message. + * If can neither list nor create: return neither-list-nor-create welcome message + * @type {string} + */ + get messageDescription() { + const canList = this.can.can('list model', this.credentialStore, { + collection: 'credential-libraries', + }); + const canCreate = this.can.can('create model', this.credentialStore, { + collection: 'credential-libraries', + }); + const resource = this.intl.t('resources.credential-library.title_plural'); + let description = 'descriptions.neither-list-nor-create'; + + if (canList) { + description = 'resources.credential-library.description'; + } else if (canCreate) { + description = 'descriptions.create-but-not-list'; + } + + return this.intl.t(description, { resource }); + } + // =actions /** diff --git a/ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/credentials/index.js b/ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/credentials/index.js index 43a7242c74..49952fbcee 100644 --- a/ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/credentials/index.js +++ b/ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/credentials/index.js @@ -16,8 +16,36 @@ export default class ScopesScopeCredentialStoresCredentialStoreCredentialsIndexC // =services @service can; + @service intl; @service router; + // =attributes + + /** + * If can list (at least): return default welcome message. + * If can create (only): return create-but-not-list welcome message. + * If can neither list nor create: return neither-list-nor-create welcome message + * @type {string} + */ + get messageDescription() { + const canList = this.can.can('list model', this.credentialStore, { + collection: 'credentials', + }); + const canCreate = this.can.can('create model', this.credentialStore, { + collection: 'credentials', + }); + const resource = this.intl.t('resources.credential.title_plural'); + let description = 'descriptions.neither-list-nor-create'; + + if (canList) { + description = 'resources.credential.description'; + } else if (canCreate) { + description = 'descriptions.create-but-not-list'; + } + + return this.intl.t(description, { resource }); + } + // =actions /** diff --git a/ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/edit-worker-filter.js b/ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/edit-worker-filter.js new file mode 100644 index 0000000000..1360495354 --- /dev/null +++ b/ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/edit-worker-filter.js @@ -0,0 +1,10 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import Controller, { inject as controller } from '@ember/controller'; + +export default class ScopesScopeCredentialStoresCredentialStoreEditWorkerFilterController extends Controller { + @controller('scopes/scope/credential-stores/index') credentialStores; +} diff --git a/ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/worker-filter.js b/ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/worker-filter.js new file mode 100644 index 0000000000..82616dedc9 --- /dev/null +++ b/ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/worker-filter.js @@ -0,0 +1,10 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import Controller, { inject as controller } from '@ember/controller'; + +export default class ScopesScopeCredentialStoresCredentialStoreWorkerFilterController extends Controller { + @controller('scopes/scope/credential-stores/index') credentialStores; +} diff --git a/ui/admin/app/controllers/scopes/scope/credential-stores/index.js b/ui/admin/app/controllers/scopes/scope/credential-stores/index.js index fd60e8f17c..0989443bc9 100644 --- a/ui/admin/app/controllers/scopes/scope/credential-stores/index.js +++ b/ui/admin/app/controllers/scopes/scope/credential-stores/index.js @@ -43,6 +43,31 @@ export default class ScopesScopeCredentialStoresIndexController extends Controll }; } + /** + * If can list (at least): return default welcome message. + * If can create (only): return create-but-not-list welcome message. + * If can neither list nor create: return neither-list-nor-create welcome message + * @type {string} + */ + get messageDescription() { + const canList = this.can.can('list model', this.scope, { + collection: 'credential-stores', + }); + const canCreate = this.can.can('create model', this.scope, { + collection: 'credential-stores', + }); + const resource = this.intl.t('resources.credential-store.title_plural'); + let description = 'descriptions.neither-list-nor-create'; + + if (canList) { + description = 'resources.credential-store.description'; + } else if (canCreate) { + description = 'descriptions.create-but-not-list'; + } + + return this.intl.t(description, { resource }); + } + // =actions /** @@ -126,4 +151,31 @@ export default class ScopesScopeCredentialStoresIndexController extends Controll this[paramKey] = [...selectedItems]; this.page = 1; } + + /** + * Save worker filter + * @param {CredentialStoreModel} credentialStore + */ + @action + @loading + @notifyError(({ message }) => message, { catch: true }) + @notifySuccess('notifications.add-success') + async saveWorkerFilter(credentialStore) { + await credentialStore.save(); + await this.router.replaceWith( + 'scopes.scope.credential-stores.credential-store.worker-filter', + ); + } + + /** + * Cancel adding or editing a worker filter + * @param {CredentialStoreModel} credentialStore + */ + @action + async cancelWorkerFilter(credentialStore) { + credentialStore.rollbackAttributes(); + await this.router.replaceWith( + 'scopes.scope.credential-stores.credential-store.worker-filter', + ); + } } diff --git a/ui/admin/app/controllers/scopes/scope/groups/index.js b/ui/admin/app/controllers/scopes/scope/groups/index.js index 7c9c215080..a428848e07 100644 --- a/ui/admin/app/controllers/scopes/scope/groups/index.js +++ b/ui/admin/app/controllers/scopes/scope/groups/index.js @@ -14,7 +14,9 @@ import { debounce } from 'core/decorators/debounce'; export default class ScopesScopeGroupsIndexController extends Controller { // =services + @service can; + @service intl; @service router; // =attributes @@ -25,6 +27,31 @@ export default class ScopesScopeGroupsIndexController extends Controller { @tracked page = 1; @tracked pageSize = 10; + /** + * If can list (at least): return default welcome message. + * If can create (only): return create-but-not-list welcome message. + * If can neither list nor create: return neither-list-nor-create welcome message + * @type {string} + */ + get messageDescription() { + const canList = this.can.can('list model', this.scope, { + collection: 'groups', + }); + const canCreate = this.can.can('create model', this.scope, { + collection: 'groups', + }); + const resource = this.intl.t('resources.group.title_plural'); + let description = 'descriptions.neither-list-nor-create'; + + if (canList) { + description = 'resources.group.description'; + } else if (canCreate) { + description = 'descriptions.create-but-not-list'; + } + + return this.intl.t(description, { resource }); + } + // =actions /** diff --git a/ui/admin/app/controllers/scopes/scope/host-catalogs/host-catalog/host-sets/index.js b/ui/admin/app/controllers/scopes/scope/host-catalogs/host-catalog/host-sets/index.js index aab8235e7e..91fff6fe47 100644 --- a/ui/admin/app/controllers/scopes/scope/host-catalogs/host-catalog/host-sets/index.js +++ b/ui/admin/app/controllers/scopes/scope/host-catalogs/host-catalog/host-sets/index.js @@ -15,8 +15,37 @@ export default class ScopesScopeHostCatalogsHostCatalogHostSetsIndexController e // =services - @service router; @service can; + @service intl; + @service router; + @service store; + + // =attributes + + /** + * If can list (at least): return default welcome message. + * If can create (only): return create-but-not-list welcome message. + * If can neither list nor create: return neither-list-nor-create welcome message + * @type {string} + */ + get messageDescription() { + const canList = this.can.can('list model', this.hostCatalog, { + collection: 'host-sets', + }); + const canCreate = this.can.can('create model', this.hostCatalog, { + collection: 'host-sets', + }); + const resource = this.intl.t('resources.host-set.title_plural'); + let description = 'descriptions.neither-list-nor-create'; + + if (canList) { + description = 'resources.host-set.description'; + } else if (canCreate) { + description = 'descriptions.create-but-not-list'; + } + + return this.intl.t(description, { resource }); + } // =actions @@ -46,8 +75,24 @@ export default class ScopesScopeHostCatalogsHostCatalogHostSetsIndexController e isNew ? 'notifications.create-success' : 'notifications.save-success', ) async save(hostSet) { + // Fetch newest host set as updates to host set attributes cause an async db update which + // updates the version again and can cause a version mismatch if the host set is updated + // again and we haven't fetched the newest version. + if (this.can.can('read host-set', hostSet)) { + const newestHostSet = await this.store.findRecord( + 'host-set', + hostSet.id, + { + reload: true, + }, + ); + + hostSet.version = newestHostSet.version; + } + await hostSet.save(); - if (this.can.can('read model', hostSet)) { + + if (this.can.can('read host-set', hostSet)) { await this.router.transitionTo( 'scopes.scope.host-catalogs.host-catalog.host-sets.host-set', hostSet, diff --git a/ui/admin/app/controllers/scopes/scope/host-catalogs/host-catalog/hosts/index.js b/ui/admin/app/controllers/scopes/scope/host-catalogs/host-catalog/hosts/index.js index 6b1165dd54..e32f7e7ea4 100644 --- a/ui/admin/app/controllers/scopes/scope/host-catalogs/host-catalog/hosts/index.js +++ b/ui/admin/app/controllers/scopes/scope/host-catalogs/host-catalog/hosts/index.js @@ -16,8 +16,36 @@ export default class ScopesScopeHostCatalogsHostCatalogHostsIndexController exte // =services @service can; + @service intl; @service router; + // =attributes + + /** + * If can list (at least): return default welcome message. + * If can create (only): return create-but-not-list welcome message. + * If can neither list nor create: return neither-list-nor-create welcome message + * @type {string} + */ + get messageDescription() { + const canList = this.can.can('list model', this.hostCatalog, { + collection: 'hosts', + }); + const canCreate = this.can.can('create model', this.hostCatalog, { + collection: 'hosts', + }); + const resource = this.intl.t('resources.host.title_plural'); + let description = 'descriptions.neither-list-nor-create'; + + if (canList) { + description = 'resources.host.description'; + } else if (canCreate) { + description = 'descriptions.create-but-not-list'; + } + + return this.intl.t(description, { resource }); + } + // =actions /** diff --git a/ui/admin/app/controllers/scopes/scope/host-catalogs/index.js b/ui/admin/app/controllers/scopes/scope/host-catalogs/index.js index 0e7a9bc6a4..d14174c288 100644 --- a/ui/admin/app/controllers/scopes/scope/host-catalogs/index.js +++ b/ui/admin/app/controllers/scopes/scope/host-catalogs/index.js @@ -16,8 +16,9 @@ import { TYPE_STATIC_CREDENTIAL } from 'api/models/host-catalog'; export default class ScopesScopeHostCatalogsIndexController extends Controller { // =services - @service router; @service can; + @service intl; + @service router; // =attributes @@ -27,6 +28,31 @@ export default class ScopesScopeHostCatalogsIndexController extends Controller { @tracked page = 1; @tracked pageSize = 10; + /** + * If can list (at least): return default welcome message. + * If can create (only): return create-but-not-list welcome message. + * If can neither list nor create: return neither-list-nor-create welcome message + * @type {string} + */ + get messageDescription() { + const canList = this.can.can('list model', this.scope, { + collection: 'host-catalogs', + }); + const canCreate = this.can.can('create model', this.scope, { + collection: 'host-catalogs', + }); + const resource = this.intl.t('resources.host-catalog.title_plural'); + let description = 'descriptions.neither-list-nor-create'; + + if (canList) { + description = 'resources.host-catalog.description'; + } else if (canCreate) { + description = 'descriptions.create-but-not-list'; + } + + return this.intl.t(description, { resource }); + } + // =actions /** diff --git a/ui/admin/app/controllers/scopes/scope/policies/index.js b/ui/admin/app/controllers/scopes/scope/policies/index.js index d1f604e1f2..e6d8a69fc5 100644 --- a/ui/admin/app/controllers/scopes/scope/policies/index.js +++ b/ui/admin/app/controllers/scopes/scope/policies/index.js @@ -14,8 +14,36 @@ export default class ScopesScopePoliciesIndexController extends Controller { // =services @service can; + @service intl; @service router; + // =attributes + + /** + * If can list (at least): return default welcome message. + * If can create (only): return create-but-not-list welcome message. + * If can neither list nor create: return neither-list-nor-create welcome message + * @type {string} + */ + get messageDescription() { + const canList = this.can.can('list model', this.scope, { + collection: 'policies', + }); + const canCreate = this.can.can('create model', this.scope, { + collection: 'policies', + }); + const resource = this.intl.t('resources.policy.title_plural'); + let description = 'descriptions.neither-list-nor-create'; + + if (canList) { + description = 'resources.policy.messages.none.description'; + } else if (canCreate) { + description = 'descriptions.create-but-not-list'; + } + + return this.intl.t(description, { resource }); + } + // =actions /** diff --git a/ui/admin/app/controllers/scopes/scope/roles/index.js b/ui/admin/app/controllers/scopes/scope/roles/index.js index 03c526243d..d569b670a5 100644 --- a/ui/admin/app/controllers/scopes/scope/roles/index.js +++ b/ui/admin/app/controllers/scopes/scope/roles/index.js @@ -17,6 +17,7 @@ export default class ScopesScopeRolesIndexController extends Controller { // =services @service can; + @service intl; @service router; // =attributes @@ -29,6 +30,31 @@ export default class ScopesScopeRolesIndexController extends Controller { grantScopeThis = GRANT_SCOPE_THIS; + /** + * If can list (at least): return default welcome message. + * If can create (only): return create-but-not-list welcome message. + * If can neither list nor create: return neither-list-nor-create welcome message + * @type {string} + */ + get messageDescription() { + const canList = this.can.can('list model', this.scope, { + collection: 'roles', + }); + const canCreate = this.can.can('create model', this.scope, { + collection: 'roles', + }); + const resource = this.intl.t('resources.role.title_plural'); + let description = 'descriptions.neither-list-nor-create'; + + if (canList) { + description = 'resources.role.description'; + } else if (canCreate) { + description = 'descriptions.create-but-not-list'; + } + + return this.intl.t(description, { resource }); + } + // =actions /** diff --git a/ui/admin/app/controllers/scopes/scope/storage-buckets/index.js b/ui/admin/app/controllers/scopes/scope/storage-buckets/index.js index 27ef587682..7c66efcb2e 100644 --- a/ui/admin/app/controllers/scopes/scope/storage-buckets/index.js +++ b/ui/admin/app/controllers/scopes/scope/storage-buckets/index.js @@ -13,8 +13,37 @@ import { notifySuccess, notifyError } from 'core/decorators/notify'; export default class ScopesScopeStorageBucketsIndexController extends Controller { // =services + @service can; + @service intl; @service router; + // =attributes + + /** + * If can list (at least): return default welcome message. + * If can create (only): return create-but-not-list welcome message. + * If can neither list nor create: return neither-list-nor-create welcome message + * @type {string} + */ + get messageDescription() { + const canList = this.can.can('list scope', this.scope, { + collection: 'storage-buckets', + }); + const canCreate = this.can.can('create scope', this.scope, { + collection: 'storage-buckets', + }); + const resource = this.intl.t('resources.storage-bucket.title_plural'); + let description = 'descriptions.neither-list-nor-create'; + + if (canList) { + description = 'resources.storage-bucket.messages.none.description'; + } else if (canCreate) { + description = 'descriptions.create-but-not-list'; + } + + return this.intl.t(description, { resource }); + } + // =actions /** diff --git a/ui/admin/app/controllers/scopes/scope/targets/index.js b/ui/admin/app/controllers/scopes/scope/targets/index.js index c4b6a2fc59..e8b6a1903d 100644 --- a/ui/admin/app/controllers/scopes/scope/targets/index.js +++ b/ui/admin/app/controllers/scopes/scope/targets/index.js @@ -72,6 +72,31 @@ export default class ScopesScopeTargetsIndexController extends Controller { })); } + /** + * If can list (at least): return default welcome message. + * If can create (only): return create-but-not-list welcome message. + * If can neither list nor create: return neither-list-nor-create welcome message + * @type {string} + */ + get messageDescription() { + const canList = this.can.can('list model', this.scope, { + collection: 'targets', + }); + const canCreate = this.can.can('create model', this.scope, { + collection: 'targets', + }); + const resource = this.intl.t('resources.target.title_plural'); + let description = 'descriptions.neither-list-nor-create'; + + if (canList) { + description = 'resources.target.description'; + } else if (canCreate) { + description = 'descriptions.create-but-not-list'; + } + + return this.intl.t(description, { resource }); + } + // =actions /** diff --git a/ui/admin/app/controllers/scopes/scope/users/index.js b/ui/admin/app/controllers/scopes/scope/users/index.js index b7fe9b7310..38d5bb8292 100644 --- a/ui/admin/app/controllers/scopes/scope/users/index.js +++ b/ui/admin/app/controllers/scopes/scope/users/index.js @@ -15,8 +15,9 @@ import { notifySuccess, notifyError } from 'core/decorators/notify'; export default class ScopesScopeUsersIndexController extends Controller { // =services - @service router; @service can; + @service intl; + @service router; // =attributes @@ -26,6 +27,31 @@ export default class ScopesScopeUsersIndexController extends Controller { @tracked page = 1; @tracked pageSize = 10; + /** + * If can list (at least): return default welcome message. + * If can create (only): return create-but-not-list welcome message. + * If can neither list nor create: return neither-list-nor-create welcome message + * @type {string} + */ + get messageDescription() { + const canList = this.can.can('list model', this.scope, { + collection: 'users', + }); + const canCreate = this.can.can('create model', this.scope, { + collection: 'users', + }); + const resource = this.intl.t('resources.user.title_plural'); + let description = 'descriptions.neither-list-nor-create'; + + if (canList) { + description = 'resources.user.description'; + } else if (canCreate) { + description = 'descriptions.create-but-not-list'; + } + + return this.intl.t(description, { resource }); + } + // =methods /** diff --git a/ui/admin/app/controllers/scopes/scope/workers/index.js b/ui/admin/app/controllers/scopes/scope/workers/index.js index 23219bb41a..6ba090f11a 100644 --- a/ui/admin/app/controllers/scopes/scope/workers/index.js +++ b/ui/admin/app/controllers/scopes/scope/workers/index.js @@ -14,13 +14,39 @@ import { notifySuccess, notifyError } from 'core/decorators/notify'; export default class ScopesScopeWorkersIndexController extends Controller { // =services - @service router; @service can; + @service intl; + @service router; // =attributes @tracked selectedWorker; + /** + * If can list (at least): return default welcome message. + * If can create (only): return create-but-not-list welcome message. + * If can neither list nor create: return neither-list-nor-create welcome message + * @type {string} + */ + get messageDescription() { + const canList = this.can.can('list worker', this.scope, { + collection: 'workers', + }); + const canCreate = this.can.can('create worker led worker', this.scope, { + collection: 'workers', + }); + const resource = this.intl.t('titles.workers'); + let description = 'descriptions.neither-list-nor-create'; + + if (canList) { + description = 'resources.worker.description'; + } else if (canCreate) { + description = 'descriptions.create-but-not-list'; + } + + return this.intl.t(description, { resource }); + } + /** * Get the first 10 tags of the selected worker. * @type {object[]} diff --git a/ui/admin/app/router.js b/ui/admin/app/router.js index 9d4a9af3ac..1656c63a61 100644 --- a/ui/admin/app/router.js +++ b/ui/admin/app/router.js @@ -150,6 +150,8 @@ Router.map(function () { function () {}, ); }); + this.route('worker-filter'); + this.route('edit-worker-filter'); }, ); this.route('new'); diff --git a/ui/admin/app/routes/scopes/index.js b/ui/admin/app/routes/scopes/index.js index e4a068f8a1..dc4a33abc1 100644 --- a/ui/admin/app/routes/scopes/index.js +++ b/ui/admin/app/routes/scopes/index.js @@ -16,7 +16,7 @@ export default class ScopesIndexRoute extends Route { // =methods /** - * If authenticated, redirects to the scope route of the authenticted scope. + * If authenticated, redirects to the scope route of the authenticated scope. * If unauthenticated, redirects to the first scope that was loaded (if any). * @param {[ScopeModel]} model * @param {?ScopeModel} model[0] diff --git a/ui/admin/app/routes/scopes/scope/authenticate/method/oidc.js b/ui/admin/app/routes/scopes/scope/authenticate/method/oidc.js index c26837df8a..a2a729bc62 100644 --- a/ui/admin/app/routes/scopes/scope/authenticate/method/oidc.js +++ b/ui/admin/app/routes/scopes/scope/authenticate/method/oidc.js @@ -33,6 +33,16 @@ export default class ScopesScopeAuthenticateMethodOidcRoute extends Route { return oidc.attemptFetchToken({ scope, authMethod }); } + /** + * Adds the auth-method to the controller context. + * @param {Controller} controller + */ + setupController(controller) { + super.setupController(...arguments); + const authMethod = this.modelFor('scopes.scope.authenticate.method'); + controller.authMethod = authMethod; + } + @runEvery(POLL_TIMEOUT_SECONDS * 1000) poller() { this.refresh(); diff --git a/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/edit-worker-filter.js b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/edit-worker-filter.js new file mode 100644 index 0000000000..751a6d0b53 --- /dev/null +++ b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/edit-worker-filter.js @@ -0,0 +1,8 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import Route from '@ember/routing/route'; + +export default class ScopesScopeCredentialStoresCredentialStoreEditWorkerFilterRoute extends Route {} diff --git a/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/worker-filter.js b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/worker-filter.js new file mode 100644 index 0000000000..0f32202e56 --- /dev/null +++ b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/worker-filter.js @@ -0,0 +1,8 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import Route from '@ember/routing/route'; + +export default class ScopesScopeCredentialStoresCredentialStoreWorkerFilterRoute extends Route {} diff --git a/ui/admin/app/styles/app.scss b/ui/admin/app/styles/app.scss index 97022c9d09..3b0997be6f 100644 --- a/ui/admin/app/styles/app.scss +++ b/ui/admin/app/styles/app.scss @@ -88,38 +88,6 @@ } } } - - .rose-form-fieldset { - .rose-table { - .rose-form-label { - left: -9999px; - position: absolute; - top: -9999px; - } - } - } -} - -.rose-table { - p { - margin-bottom: 0; - } - - .rose-table-header-cell { - p { - color: var(--ui-gray-subtler-1); - font-weight: normal; - } - } - - .rose-form-checkbox { - margin-bottom: 0; - - .rose-form-checkbox-label-text { - color: var(--stark); - text-decoration: underline; - } - } } .rose-header { @@ -1088,3 +1056,30 @@ padding: 0.25rem 0.75rem; } } + +// worker filter generator +.filter-generator-selection { + .hds-form-group__control-field { + margin-bottom: 0; + } +} + +.formatted-results { + p { + margin-bottom: 0.5rem; + } +} + +.generated-results-container { + display: flex; + flex-direction: row; + gap: 0.5rem; + align-items: flex-start; +} + +// storage buckets +.storage-bucket-worker-filter { + .hds-form-field--layout-flag { + margin-bottom: 0; + } +} diff --git a/ui/admin/app/templates/application.hbs b/ui/admin/app/templates/application.hbs index 634233c477..7374139aa1 100644 --- a/ui/admin/app/templates/application.hbs +++ b/ui/admin/app/templates/application.hbs @@ -132,33 +132,36 @@ {{/if}} - - -

{{confirmation.text}}

-
- - - {{if - confirmation.options.confirm - (t confirmation.options.confirm) - (t 'actions.ok') - }} - - - {{t 'actions.cancel'}} - - -
+ + + {{if + confirmation.options.title + (t confirmation.options.title) + (t 'actions.confirm') + }} + + + + {{confirmation.text}} + + + + + + + + +
\ No newline at end of file diff --git a/ui/admin/app/templates/scopes/index.hbs b/ui/admin/app/templates/scopes/index.hbs index c162228035..5f7437f286 100644 --- a/ui/admin/app/templates/scopes/index.hbs +++ b/ui/admin/app/templates/scopes/index.hbs @@ -10,15 +10,13 @@
- - - {{t 'auth.descriptions.no-scopes'}} - - + + + +
diff --git a/ui/admin/app/templates/scopes/scope.hbs b/ui/admin/app/templates/scopes/scope.hbs index 58210daaa4..ab94e6edce 100644 --- a/ui/admin/app/templates/scopes/scope.hbs +++ b/ui/admin/app/templates/scopes/scope.hbs @@ -83,7 +83,7 @@ }} - {{t 'resources.worker.title_plural'}} + {{t 'titles.workers'}} {{/if}} diff --git a/ui/admin/app/templates/scopes/scope/aliases/index.hbs b/ui/admin/app/templates/scopes/scope/aliases/index.hbs index d4cc1d7a3b..1b5c53fed2 100644 --- a/ui/admin/app/templates/scopes/scope/aliases/index.hbs +++ b/ui/admin/app/templates/scopes/scope/aliases/index.hbs @@ -170,23 +170,7 @@ {{else}} - + {{#if (can 'create model' this.scope collection='aliases')}} - - - - {{#if (can 'list model' this.authMethod collection='accounts')}} - {{! can list (at least): show default welcome message}} - {{t 'resources.account.description'}} - - {{else if - (can 'create model' this.authMethod collection='accounts') - }} - {{! can create (only): show create-but-not-list welcome message}} - {{t - 'descriptions.create-but-not-list' - resource=(t 'resources.account.title_plural') - }} - {{else}} - {{! can neither list nor create }} - {{t - 'descriptions.neither-list-nor-create' - resource=(t 'resources.account.title_plural') - }} - {{/if}} - - - + + + {{#if (can 'create model' this.authMethod collection='accounts')}} - - - - {{t 'resources.account.actions.create'}} - + + + {{/if}} - + {{/if}} diff --git a/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/managed-groups/index.hbs b/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/managed-groups/index.hbs index 3ccb19705f..298ea6b1ec 100644 --- a/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/managed-groups/index.hbs +++ b/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/managed-groups/index.hbs @@ -108,45 +108,23 @@ {{/if}} {{else}} - - - - {{#if - (can 'list model' this.authMethod collection='managed-groups') - }} - {{! Can list (at least): show default welcome message }} - {{t 'resources.managed-group.description'}} - {{else if - (can 'create model' this.authMethod collection='managed-groups') - }} - {{! Can create only: show create but not list welcome message }} - {{t - 'descriptions.create-but-not-list' - resource=(t 'resources.managed-group.title_plural') - }} - {{else}} - {{! Can neither list nor create }} - {{t - 'descriptions.neither-list-nor-create' - resource=(t 'resources.managed-group.title_plural') - }} - {{/if}} - - + + + {{#if (can 'create model' this.authMethod collection='managed-groups') }} - - - {{t 'resources.managed-group.actions.create'}} - + + + {{/if}} - + {{/if}} diff --git a/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/managed-groups/managed-group/members.hbs b/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/managed-groups/managed-group/members.hbs index a03e4580aa..e1c9bcecaa 100644 --- a/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/managed-groups/managed-group/members.hbs +++ b/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/managed-groups/managed-group/members.hbs @@ -102,14 +102,14 @@ {{/if}} {{else}} - - - {{t 'resources.managed-group.member.messages.description'}} - - + + + + {{/if}} diff --git a/ui/admin/app/templates/scopes/scope/auth-methods/index.hbs b/ui/admin/app/templates/scopes/scope/auth-methods/index.hbs index 4222f2cf2b..490aeab83e 100644 --- a/ui/admin/app/templates/scopes/scope/auth-methods/index.hbs +++ b/ui/admin/app/templates/scopes/scope/auth-methods/index.hbs @@ -149,7 +149,6 @@ /> {{#unless B.data.isPrimary}} {{t 'resources.auth-method.actions.make-primary'}} @@ -174,7 +173,7 @@ @currentPageSize={{this.pageSize}} /> {{else}} - + - + {{/if}} diff --git a/ui/admin/app/templates/scopes/scope/authenticate.hbs b/ui/admin/app/templates/scopes/scope/authenticate.hbs index 7dfbfdb9e8..954fa272ee 100644 --- a/ui/admin/app/templates/scopes/scope/authenticate.hbs +++ b/ui/admin/app/templates/scopes/scope/authenticate.hbs @@ -39,8 +39,7 @@ {{/each}} - - + {{#each @model.authMethods as |authMethod|}} @@ -63,15 +62,15 @@ {{/if}} {{#unless @model.authMethods}} - - - {{t 'resources.auth-method.messages.none.description'}} - - + + + + {{/unless}} diff --git a/ui/admin/app/templates/scopes/scope/authenticate/index.hbs b/ui/admin/app/templates/scopes/scope/authenticate/index.hbs index e7be45d82c..0dc8d659ba 100644 --- a/ui/admin/app/templates/scopes/scope/authenticate/index.hbs +++ b/ui/admin/app/templates/scopes/scope/authenticate/index.hbs @@ -5,14 +5,12 @@ {{#unless @model}} - - - {{t 'resources.auth-method.messages.none.description'}} - - + + + + {{/unless}} \ No newline at end of file diff --git a/ui/admin/app/templates/scopes/scope/authenticate/method/oidc.hbs b/ui/admin/app/templates/scopes/scope/authenticate/method/oidc.hbs index 01d1d52560..002c73bfe2 100644 --- a/ui/admin/app/templates/scopes/scope/authenticate/method/oidc.hbs +++ b/ui/admin/app/templates/scopes/scope/authenticate/method/oidc.hbs @@ -12,7 +12,7 @@

{{t 'resources.auth-method.messages.pending.description'}}

{{t 'resources.auth-method.questions.no-see-window'}} - + {{t 'actions.retry'}}

diff --git a/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credential-libraries/index.hbs b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credential-libraries/index.hbs index 6db50d7bb6..c89c1c78c4 100644 --- a/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credential-libraries/index.hbs +++ b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credential-libraries/index.hbs @@ -76,42 +76,11 @@ {{else}} - - - {{#if - (can - 'list model' - this.credentialStore - collection='credential-libraries' - ) - }} - {{! can list (at least): show default welcome message}} - {{t 'resources.credential-library.description'}} - - {{else if - (can - 'create model' - this.credentialStore - collection='credential-libraries' - ) - }} - {{! can create (only): show create-but-not-list welcome message}} - {{t - 'descriptions.create-but-not-list' - resource=(t 'resources.credential-library.title_plural') - }} - {{else}} - {{! can neither list nor create }} - {{t - 'descriptions.neither-list-nor-create' - resource=(t 'resources.credential-library.title_plural') - }} - {{/if}} - - + + + {{#if (can 'create model' @@ -119,14 +88,15 @@ collection='credential-libraries' ) }} - - - {{t 'titles.new'}} - + + + {{/if}} - +
{{/if}} diff --git a/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credentials/index.hbs b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credentials/index.hbs index c37dd5ba09..34bfab32e4 100644 --- a/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credentials/index.hbs +++ b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credentials/index.hbs @@ -73,46 +73,21 @@ {{else}} - - - {{#if - (can 'list model' this.credentialStore collection='credentials') - }} - {{! can list (at least): show default welcome message}} - {{t 'resources.credential.description'}} - - {{else if - (can 'create model' this.credentialStore collection='credentials') - }} - {{! can create (only): show create-but-not-list welcome message}} - {{t - 'descriptions.create-but-not-list' - resource=(t 'resources.credential.title_plural') - }} - {{else}} - {{! can neither list nor create }} - {{t - 'descriptions.neither-list-nor-create' - resource=(t 'resources.credential.title_plural') - }} - {{/if}} - - - + + + {{#if (can 'create model' this.credentialStore collection='credentials') }} - - - {{t 'titles.new'}} - + + + {{/if}} - + {{/if}} diff --git a/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/edit-worker-filter.hbs b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/edit-worker-filter.hbs new file mode 100644 index 0000000000..ec1f89d482 --- /dev/null +++ b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/edit-worker-filter.hbs @@ -0,0 +1,35 @@ +{{! + Copyright (c) HashiCorp, Inc. + SPDX-License-Identifier: BUSL-1.1 +}} + +{{page-title (t 'actions.edit-worker-filter')}} + + + + + + + + + + {{t 'actions.edit-worker-filter'}} + + + + {{t 'resources.credential-store.worker-filter.messages.edit.description'}} + + + + + + + \ No newline at end of file diff --git a/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/worker-filter.hbs b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/worker-filter.hbs new file mode 100644 index 0000000000..ad9332253e --- /dev/null +++ b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/worker-filter.hbs @@ -0,0 +1,66 @@ +{{! + Copyright (c) HashiCorp, Inc. + SPDX-License-Identifier: BUSL-1.1 +}} + +{{page-title (t 'form.worker_filter.label')}} + + + + + + + + + + + + + + + + + + + + + {{#if @model.worker_filter}} + + {{t 'form.worker_filter.label'}} + + {{t 'resources.credential-store.worker-filter.description'}} + + + {{else}} + + + + + + + + + + {{/if}} + + \ No newline at end of file diff --git a/ui/admin/app/templates/scopes/scope/credential-stores/index.hbs b/ui/admin/app/templates/scopes/scope/credential-stores/index.hbs index 01072888b1..38ac24bcdc 100644 --- a/ui/admin/app/templates/scopes/scope/credential-stores/index.hbs +++ b/ui/admin/app/templates/scopes/scope/credential-stores/index.hbs @@ -124,23 +124,7 @@ - + {{#if (can 'create model' this.scope collection='credential-stores')}} {{else}} - - - {{t 'resources.scope.messages.cannot_read'}} - - + + + {{/if}} diff --git a/ui/admin/app/templates/scopes/scope/error.hbs b/ui/admin/app/templates/scopes/scope/error.hbs index cf83a0292d..90c841b4a4 100644 --- a/ui/admin/app/templates/scopes/scope/error.hbs +++ b/ui/admin/app/templates/scopes/scope/error.hbs @@ -7,6 +7,9 @@ {{#each @model.errors as |error|}} - + {{/each}} \ No newline at end of file diff --git a/ui/admin/app/templates/scopes/scope/groups/group/members.hbs b/ui/admin/app/templates/scopes/scope/groups/group/members.hbs index b10c842770..3a5e44ca9b 100644 --- a/ui/admin/app/templates/scopes/scope/groups/group/members.hbs +++ b/ui/admin/app/templates/scopes/scope/groups/group/members.hbs @@ -90,18 +90,17 @@ {{else}} - - - {{t 'resources.group.messages.members.description'}} - - - - {{t 'resources.group.actions.add-members'}} - - + + + + + + + {{/if}} diff --git a/ui/admin/app/templates/scopes/scope/groups/index.hbs b/ui/admin/app/templates/scopes/scope/groups/index.hbs index 265e3a1bfb..bbbd006066 100644 --- a/ui/admin/app/templates/scopes/scope/groups/index.hbs +++ b/ui/admin/app/templates/scopes/scope/groups/index.hbs @@ -98,23 +98,7 @@ {{else}} - + {{#if (can 'create model' this.scope collection='groups')}} - - - {{t 'resources.host-set.host.messages.none.description'}} - - + + + + {{/unless}} diff --git a/ui/admin/app/templates/scopes/scope/host-catalogs/host-catalog/host-sets/index.hbs b/ui/admin/app/templates/scopes/scope/host-catalogs/host-catalog/host-sets/index.hbs index 1aa9aa7586..bb1c70832a 100644 --- a/ui/admin/app/templates/scopes/scope/host-catalogs/host-catalog/host-sets/index.hbs +++ b/ui/admin/app/templates/scopes/scope/host-catalogs/host-catalog/host-sets/index.hbs @@ -69,42 +69,19 @@ {{else}} - - - - {{#if (can 'list model' this.hostCatalog collection='host-sets')}} - {{! can list (at least): show default welcome message}} - {{t 'resources.host-set.description'}} - - {{else if - (can 'create model' this.hostCatalog collection='host-sets') - }} - {{! can create (only): show create-but-not-list welcome message}} - {{t - 'descriptions.create-but-not-list' - resource=(t 'resources.host-set.title_plural') - }} - {{else}} - {{! can neither list nor create }} - {{t - 'descriptions.neither-list-nor-create' - resource=(t 'resources.host-set.title_plural') - }} - {{/if}} - - + + + {{#if (can 'create model' this.hostCatalog collection='host-sets')}} - - - {{t 'titles.new'}} - + + + {{/if}} - + {{/if}} diff --git a/ui/admin/app/templates/scopes/scope/host-catalogs/host-catalog/hosts/index.hbs b/ui/admin/app/templates/scopes/scope/host-catalogs/host-catalog/hosts/index.hbs index 87188d295c..3599272033 100644 --- a/ui/admin/app/templates/scopes/scope/host-catalogs/host-catalog/hosts/index.hbs +++ b/ui/admin/app/templates/scopes/scope/host-catalogs/host-catalog/hosts/index.hbs @@ -69,39 +69,19 @@ {{else}} - - - {{#if (can 'list model' this.hostCatalog collection='hosts')}} - {{! can list (at least): show default welcome message}} - {{t 'resources.host.description'}} - - {{else if (can 'create model' this.hostCatalog collection='hosts')}} - {{! can create (only): show create-but-not-list welcome message}} - {{t - 'descriptions.create-but-not-list' - resource=(t 'resources.host.title_plural') - }} - {{else}} - {{! can neither list nor create }} - {{t - 'descriptions.neither-list-nor-create' - resource=(t 'resources.host.title_plural') - }} - {{/if}} - - + + + {{#if (can 'create model' this.hostCatalog collection='hosts')}} - - - {{t 'titles.new'}} - + + + {{/if}} - + {{/if}} diff --git a/ui/admin/app/templates/scopes/scope/host-catalogs/index.hbs b/ui/admin/app/templates/scopes/scope/host-catalogs/index.hbs index 3214d396f9..13a5874b9a 100644 --- a/ui/admin/app/templates/scopes/scope/host-catalogs/index.hbs +++ b/ui/admin/app/templates/scopes/scope/host-catalogs/index.hbs @@ -105,23 +105,7 @@ - + {{#if (can 'create model' this.scope collection='host-catalogs')}} - {{/if}} diff --git a/ui/admin/app/templates/scopes/scope/policies/index.hbs b/ui/admin/app/templates/scopes/scope/policies/index.hbs index 34b5f8d99c..f8ba3c3373 100644 --- a/ui/admin/app/templates/scopes/scope/policies/index.hbs +++ b/ui/admin/app/templates/scopes/scope/policies/index.hbs @@ -121,34 +121,19 @@ {{else}} - - - {{#if (can 'list model' this.scope collection='policies')}} - {{t 'resources.policy.messages.none.description'}} - {{else if (can 'create model' this.scope collection='policies')}} - {{t - 'descriptions.create-but-not-list' - resource=(t 'resources.policy.title_plural') - }} - {{else}} - {{t - 'descriptions.neither-list-nor-create' - resource=(t 'resources.policy.title_plural') - }} - {{/if}} - + + + {{#if (can 'create model' this.scope collection='policies')}} - + + + {{/if}} - + {{/if}} diff --git a/ui/admin/app/templates/scopes/scope/roles/index.hbs b/ui/admin/app/templates/scopes/scope/roles/index.hbs index 29efa31a61..e3df1297f6 100644 --- a/ui/admin/app/templates/scopes/scope/roles/index.hbs +++ b/ui/admin/app/templates/scopes/scope/roles/index.hbs @@ -108,23 +108,7 @@ {{else}} - + {{#if (can 'create model' this.scope collection='roles')}} - - - {{t 'resources.role.principal.messages.welcome.description'}} - - - - {{t 'resources.role.principal.actions.add-principals'}} - - + + + + + + + {{/if}} diff --git a/ui/admin/app/templates/scopes/scope/storage-buckets/index.hbs b/ui/admin/app/templates/scopes/scope/storage-buckets/index.hbs index 6104e2a1a9..e607ad0e5c 100644 --- a/ui/admin/app/templates/scopes/scope/storage-buckets/index.hbs +++ b/ui/admin/app/templates/scopes/scope/storage-buckets/index.hbs @@ -133,36 +133,21 @@ {{else}} - - - {{#if (can 'list scope' this.scope collection='storage-buckets')}} - {{t 'resources.storage-bucket.messages.none.description'}} - {{else if - (can 'create scope' this.scope collection='storage-buckets') - }} - {{t - 'descriptions.create-but-not-list' - resource=(t 'resources.storage-bucket.title_plural') - }} - {{else}} - {{t - 'descriptions.neither-list-nor-create' - resource=(t 'resources.storage-bucket.title_plural') - }} - {{/if}} - + + + {{#if (can 'create scope' this.scope collection='storage-buckets')}} - + + + {{/if}} - + {{/if}} diff --git a/ui/admin/app/templates/scopes/scope/targets/index.hbs b/ui/admin/app/templates/scopes/scope/targets/index.hbs index cb976bdfb7..582faa472d 100644 --- a/ui/admin/app/templates/scopes/scope/targets/index.hbs +++ b/ui/admin/app/templates/scopes/scope/targets/index.hbs @@ -185,23 +185,7 @@ {{else}} - + {{#if (can 'create model' this.scope collection='targets')}} - - - {{t + + + - - - {{t 'actions.back'}} - - + /> + + + + {{/unless}} diff --git a/ui/admin/app/templates/scopes/scope/targets/target/add-host-sources.hbs b/ui/admin/app/templates/scopes/scope/targets/target/add-host-sources.hbs index 6f5114fd25..600fad182d 100644 --- a/ui/admin/app/templates/scopes/scope/targets/target/add-host-sources.hbs +++ b/ui/admin/app/templates/scopes/scope/targets/target/add-host-sources.hbs @@ -24,32 +24,13 @@ - {{#if @model.hostSets}} - - {{/if}} - - {{#unless @model.hostSets}} - - - - {{t 'resources.target.host-source.messages.none.description'}} - - - - {{t 'actions.back'}} - - - - {{/unless}} + \ No newline at end of file diff --git a/ui/admin/app/templates/scopes/scope/targets/target/add-injected-application-credential-sources.hbs b/ui/admin/app/templates/scopes/scope/targets/target/add-injected-application-credential-sources.hbs index 5e0c12f696..8b05ee1e87 100644 --- a/ui/admin/app/templates/scopes/scope/targets/target/add-injected-application-credential-sources.hbs +++ b/ui/admin/app/templates/scopes/scope/targets/target/add-injected-application-credential-sources.hbs @@ -41,24 +41,25 @@ {{#unless this.hasAvailableInjectedApplicationCredentialSources}} - - - {{t + + + - - - {{t 'actions.back'}} - - + /> + + + + {{/unless}} diff --git a/ui/admin/app/templates/scopes/scope/targets/target/brokered-credential-sources.hbs b/ui/admin/app/templates/scopes/scope/targets/target/brokered-credential-sources.hbs index 4adf3821a8..288265f4dc 100644 --- a/ui/admin/app/templates/scopes/scope/targets/target/brokered-credential-sources.hbs +++ b/ui/admin/app/templates/scopes/scope/targets/target/brokered-credential-sources.hbs @@ -106,24 +106,27 @@ {{else}} - - - {{t + + + - - - {{t 'resources.target.actions.add-brokered-credential-sources'}} - - + /> + + + + {{/if}} diff --git a/ui/admin/app/templates/scopes/scope/targets/target/edit-egress-worker-filter.hbs b/ui/admin/app/templates/scopes/scope/targets/target/edit-egress-worker-filter.hbs index 21a17a612f..3a5778062c 100644 --- a/ui/admin/app/templates/scopes/scope/targets/target/edit-egress-worker-filter.hbs +++ b/ui/admin/app/templates/scopes/scope/targets/target/edit-egress-worker-filter.hbs @@ -17,7 +17,7 @@

{{t 'resources.target.workers.edit-egress-worker-filter.title'}} - +

{{t 'resources.target.workers.edit-egress-worker-filter.description' @@ -25,7 +25,7 @@ -

{{t 'resources.target.workers.edit-ingress-worker-filter.title'}} - +

{{t 'resources.target.workers.edit-ingress-worker-filter.description' @@ -26,7 +26,7 @@ - - - - {{t 'resources.target.host-source.messages.welcome.description'}} - - - - {{t 'resources.target.actions.add-host-sources'}} - - + + + + + + + {{/if}} diff --git a/ui/admin/app/templates/scopes/scope/targets/target/injected-application-credential-sources.hbs b/ui/admin/app/templates/scopes/scope/targets/target/injected-application-credential-sources.hbs index 0a4e31f41b..04fd1cf4f6 100644 --- a/ui/admin/app/templates/scopes/scope/targets/target/injected-application-credential-sources.hbs +++ b/ui/admin/app/templates/scopes/scope/targets/target/injected-application-credential-sources.hbs @@ -110,26 +110,27 @@ {{else}} - - - {{t - 'resources.target.injected-application-credential-source.messages.welcome.description' + + - - - {{t - 'resources.target.actions.add-injected-application-credential-sources' + /> + - + /> + + + + {{/if}} diff --git a/ui/admin/app/templates/scopes/scope/targets/target/workers.hbs b/ui/admin/app/templates/scopes/scope/targets/target/workers.hbs index eb51a2ee32..8c41131002 100644 --- a/ui/admin/app/templates/scopes/scope/targets/target/workers.hbs +++ b/ui/admin/app/templates/scopes/scope/targets/target/workers.hbs @@ -3,9 +3,9 @@ SPDX-License-Identifier: BUSL-1.1 }} -{{page-title (t 'resources.target.workers.title')}} +{{page-title (t 'titles.workers')}} @@ -51,9 +51,13 @@ 'resources.target.workers.accordion-label.ingress-workers' }} @@ -62,23 +66,20 @@ <:content> {{#if @model.ingress_worker_filter}} - {{t - 'resources.target.workers.worker-filter.title' - }} + {{t 'form.worker_filter.label'}} {{t 'resources.target.workers.worker-filter.description' }} {{else}} - + @@ -110,23 +117,20 @@ <:content> {{#if @model.egress_worker_filter}} - {{t - 'resources.target.workers.worker-filter.title' - }} + {{t 'form.worker_filter.label'}} {{t 'resources.target.workers.worker-filter.description' }} {{else}} - + - + {{#if (can 'create model' this.scope collection='users')}} - - - {{t 'resources.user.messages.accounts.description'}} - - - - {{t 'resources.user.actions.add-accounts'}} - - + + + + + + + {{/if}} diff --git a/ui/admin/app/templates/scopes/scope/users/user/add-accounts.hbs b/ui/admin/app/templates/scopes/scope/users/user/add-accounts.hbs index 0b6ae827dc..ed0f2deddf 100644 --- a/ui/admin/app/templates/scopes/scope/users/user/add-accounts.hbs +++ b/ui/admin/app/templates/scopes/scope/users/user/add-accounts.hbs @@ -21,32 +21,13 @@ - {{#if @model.accounts}} - - {{/if}} - - {{#unless @model.accounts}} - - - - {{t 'resources.user.messages.no-accounts.description'}} - - - - {{t 'actions.back'}} - - - - {{/unless}} + \ No newline at end of file diff --git a/ui/admin/app/templates/scopes/scope/workers.hbs b/ui/admin/app/templates/scopes/scope/workers.hbs index 57d97163fd..5f7cc1edb2 100644 --- a/ui/admin/app/templates/scopes/scope/workers.hbs +++ b/ui/admin/app/templates/scopes/scope/workers.hbs @@ -3,10 +3,7 @@ SPDX-License-Identifier: BUSL-1.1 }} -{{page-title (t 'resources.worker.title_plural')}} - +{{page-title (t 'titles.workers')}} + {{outlet}} \ No newline at end of file diff --git a/ui/admin/app/templates/scopes/scope/workers/index.hbs b/ui/admin/app/templates/scopes/scope/workers/index.hbs index bf0879ce9b..6d1b24bd23 100644 --- a/ui/admin/app/templates/scopes/scope/workers/index.hbs +++ b/ui/admin/app/templates/scopes/scope/workers/index.hbs @@ -11,7 +11,7 @@

- {{t 'resources.worker.title_plural'}} + {{t 'titles.workers'}}

@@ -200,39 +200,21 @@ {{/if}} {{else}} - - - {{#if (can 'list worker' this.scope collection='workers')}} - {{! can list (at least): show default welcome message}} - {{t 'resources.worker.description'}} - {{else if - (can 'create worker led worker' this.scope collection='workers') - }} - {{! can create (only): show create-but-not-list welcome message}} - {{t - 'descriptions.create-but-not-list' - resource=(t 'resources.worker.title_plural') - }} - {{else}} - {{! can neither list nor create }} - {{t - 'descriptions.neither-list-nor-create' - resource=(t 'resources.worker.title_plural') - }} - {{/if}} - + + + {{#if (can 'create worker led worker' this.scope collection='workers') }} - - - {{t 'titles.new'}} - + + + {{/if}} - + {{/if}} diff --git a/ui/admin/config/environment.js b/ui/admin/config/environment.js index 5d7e0b26ad..694597b6ba 100644 --- a/ui/admin/config/environment.js +++ b/ui/admin/config/environment.js @@ -101,8 +101,8 @@ module.exports = function (environment) { 'target.add-host-sources': '/docs/concepts/domain-model/host-sets', 'target.enable-session-recording': '/docs/configuration/session-recording/enable-session-recording', - 'target.worker-filters': - '/docs/concepts/filtering/worker-tags#worker-filtering', + 'worker-filters-format': + '/docs/concepts/filtering/worker-tags#filter-examples', user: '/docs/concepts/domain-model/users', downloads: '/install', 'getting-started.desktop': @@ -131,6 +131,7 @@ module.exports = function (environment) { 'storage-policy.update': '/docs/configuration/session-recording/update-storage-policy', alias: '/docs/concepts/aliases', + 'support-page': 'https://support.hashicorp.com/hc/en-us', }, }, diff --git a/ui/admin/config/features.js b/ui/admin/config/features.js index 5c0908787e..37e3a6483f 100644 --- a/ui/admin/config/features.js +++ b/ui/admin/config/features.js @@ -28,7 +28,7 @@ const baseEdition = { 'target-network-address': false, 'vault-worker-filter': false, 'ldap-auth-methods': false, - 'dynamic-credentials-worker-filter': false, + 'host-catalog-worker-filter': false, }; // Editions maps edition keys to their associated featuresets. const featureEditions = {}; @@ -44,7 +44,7 @@ featureEditions.enterprise = { ...featureEditions.oss, 'target-worker-filters-v2-ingress': true, 'vault-worker-filter': true, - 'dynamic-credentials-worker-filter': true, + 'host-catalog-worker-filter': true, }; featureEditions.hcp = { ...featureEditions.enterprise, diff --git a/ui/admin/package.json b/ui/admin/package.json index adafd5834b..4e3943b7b5 100644 --- a/ui/admin/package.json +++ b/ui/admin/package.json @@ -10,7 +10,7 @@ "test": "tests" }, "scripts": { - "doc:toc": "doctoc README.md tests/e2e/README.md", + "doc:toc": "doctoc README.md", "build:development": "ember build", "build": "ember build --environment=production", "build:oss": "EDITION=oss ember build --environment=production", @@ -37,7 +37,8 @@ }, "dependencies": { "ember-named-blocks-polyfill": "^0.2.5", - "lodash": "^4.17.21" + "lodash": "^4.17.21", + "uuid": "^11.0.3" }, "devDependencies": { "@babel/core": "^7.25.2", @@ -52,7 +53,6 @@ "@glimmer/component": "^1.1.2", "@glimmer/tracking": "^1.1.2", "@hashicorp/ember-asciinema-player": "https://github.com/hashicorp/ember-asciinema-player.git#e047a096039cff70234c232efe75dcad74c6358a", - "@playwright/test": "^1.43.0", "babel-loader": "^9.2.1", "broccoli-asset-rev": "^3.0.0", "concurrently": "^9.1.0", @@ -61,7 +61,6 @@ "ember-a11y-testing": "^7.0.0", "ember-auto-import": "^2.8.1", "ember-cli": "^5.12.0", - "ember-cli-app-version": "^6.0.0", "ember-cli-babel": "^8.2.0", "ember-cli-code-coverage": "^1.0.3", "ember-cli-content-security-policy": "^2.0.3", @@ -94,7 +93,7 @@ "qunit": "^2.22.0", "qunit-dom": "^3.2.1", "rose": "*", - "sinon": "^18.0.0", + "sinon": "^19.0.2", "stylelint": "^15.10.1", "stylelint-config-prettier-scss": "^0.0.1", "stylelint-config-standard-scss": "^11.0.0", diff --git a/ui/admin/tests/acceptance/accounts/change-password-test.js b/ui/admin/tests/acceptance/accounts/change-password-test.js index 034636019e..4787620175 100644 --- a/ui/admin/tests/acceptance/accounts/change-password-test.js +++ b/ui/admin/tests/acceptance/accounts/change-password-test.js @@ -5,15 +5,13 @@ import { module, test } from 'qunit'; import { visit, currentURL, click, fillIn } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { Response } from 'miragejs'; import { authenticateSession, - // These are left here intentionally for future reference. - // currentSession, invalidateSession, } from 'ember-simple-auth/test-support'; import * as selectors from './selectors'; @@ -36,7 +34,7 @@ module('Acceptance | accounts | change password', function (hooks) { changePassword: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', @@ -48,7 +46,7 @@ module('Acceptance | accounts | change password', function (hooks) { instances.account = this.server.create('account', { scope: instances.scopes.org, }); - authenticateSession({ + await authenticateSession({ account_id: instances.account.id, username: 'admin', }); @@ -163,7 +161,7 @@ module('Acceptance | accounts | change password', function (hooks) { }); test('cannot change password when not authenticated', async function (assert) { - invalidateSession(); + await invalidateSession(); await visit(urls.changePassword); diff --git a/ui/admin/tests/acceptance/accounts/create-test.js b/ui/admin/tests/acceptance/accounts/create-test.js index b1d7481857..0ce845a6fa 100644 --- a/ui/admin/tests/acceptance/accounts/create-test.js +++ b/ui/admin/tests/acceptance/accounts/create-test.js @@ -5,16 +5,11 @@ import { module, test } from 'qunit'; import { visit, currentURL, click, find, fillIn } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { Response } from 'miragejs'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; import * as selectors from './selectors'; import * as commonSelectors from 'admin/tests/helpers/selectors'; @@ -40,8 +35,8 @@ module('Acceptance | accounts | create', function (hooks) { account: null, }; - hooks.beforeEach(function () { - authenticateSession({ username: 'admin' }); + hooks.beforeEach(async function () { + await authenticateSession({ username: 'admin' }); instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', diff --git a/ui/admin/tests/acceptance/accounts/delete-test.js b/ui/admin/tests/acceptance/accounts/delete-test.js index 68fa74aafe..c0cc9e9506 100644 --- a/ui/admin/tests/acceptance/accounts/delete-test.js +++ b/ui/admin/tests/acceptance/accounts/delete-test.js @@ -5,16 +5,11 @@ import { module, test } from 'qunit'; import { visit, click, find } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { Response } from 'miragejs'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; import * as selectors from './selectors'; import * as commonSelectors from 'admin/tests/helpers/selectors'; @@ -38,8 +33,8 @@ module('Acceptance | accounts | delete', function (hooks) { account: null, }; - hooks.beforeEach(function () { - authenticateSession({ username: 'admin' }); + hooks.beforeEach(async function () { + await authenticateSession({ username: 'admin' }); instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', diff --git a/ui/admin/tests/acceptance/accounts/list-test.js b/ui/admin/tests/acceptance/accounts/list-test.js index f5ba216fb5..57396a8c36 100644 --- a/ui/admin/tests/acceptance/accounts/list-test.js +++ b/ui/admin/tests/acceptance/accounts/list-test.js @@ -5,14 +5,9 @@ import { module, test } from 'qunit'; import { visit, click } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; import * as selectors from './selectors'; import * as commonSelectors from 'admin/tests/helpers/selectors'; @@ -36,8 +31,8 @@ module('Acceptance | accounts | list', function (hooks) { account: null, }; - hooks.beforeEach(function () { - authenticateSession({}); + hooks.beforeEach(async function () { + await authenticateSession({}); instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', diff --git a/ui/admin/tests/acceptance/accounts/read-test.js b/ui/admin/tests/acceptance/accounts/read-test.js index b702647b92..e640f312fd 100644 --- a/ui/admin/tests/acceptance/accounts/read-test.js +++ b/ui/admin/tests/acceptance/accounts/read-test.js @@ -5,15 +5,10 @@ import { module, test } from 'qunit'; import { visit, currentURL, click } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; import * as commonSelectors from 'admin/tests/helpers/selectors'; module('Acceptance | accounts | read', function (hooks) { @@ -35,8 +30,8 @@ module('Acceptance | accounts | read', function (hooks) { account: null, }; - hooks.beforeEach(function () { - authenticateSession({ username: 'admin' }); + hooks.beforeEach(async function () { + await authenticateSession({ username: 'admin' }); instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', @@ -58,7 +53,7 @@ module('Acceptance | accounts | read', function (hooks) { test('can navigate to an account form', async function (assert) { await visit(urls.accounts); - await click(commonSelectors.TABLE_FIRST_ROW_RESOURCE_LINK); + await click(commonSelectors.TABLE_RESOURCE_LINK(urls.account)); await a11yAudit(); assert.strictEqual(currentURL(), urls.account); @@ -69,10 +64,12 @@ module('Acceptance | accounts | read', function (hooks) { instances.account.authorized_actions.filter((item) => item !== 'read'); await visit(urls.accounts); - assert.dom(commonSelectors.TABLE_FIRST_ROW_RESOURCE_LINK).doesNotExist(); + assert + .dom(commonSelectors.TABLE_RESOURCE_LINK(urls.account)) + .doesNotExist(); }); - test('user can navigate to account and incorrect url autocorrects', async function (assert) { + test('user can navigate to account and incorrect url auto-corrects', async function (assert) { const authMethod = this.server.create('auth-method', { scope: instances.scopes.org, }); diff --git a/ui/admin/tests/acceptance/accounts/set-password-test.js b/ui/admin/tests/acceptance/accounts/set-password-test.js index 86d4932e49..7e6d6c40cf 100644 --- a/ui/admin/tests/acceptance/accounts/set-password-test.js +++ b/ui/admin/tests/acceptance/accounts/set-password-test.js @@ -5,16 +5,11 @@ import { module, test } from 'qunit'; import { visit, currentURL, click, fillIn } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { Response } from 'miragejs'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; import * as selectors from './selectors'; import * as commonSelectors from 'admin/tests/helpers/selectors'; @@ -37,8 +32,8 @@ module('Acceptance | accounts | set password', function (hooks) { setPassword: null, }; - hooks.beforeEach(function () { - authenticateSession({ username: 'admin' }); + hooks.beforeEach(async function () { + await authenticateSession({ username: 'admin' }); instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', diff --git a/ui/admin/tests/acceptance/accounts/update-test.js b/ui/admin/tests/acceptance/accounts/update-test.js index 360ddd1d26..af3195f338 100644 --- a/ui/admin/tests/acceptance/accounts/update-test.js +++ b/ui/admin/tests/acceptance/accounts/update-test.js @@ -5,16 +5,11 @@ import { module, test } from 'qunit'; import { visit, click, fillIn } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { Response } from 'miragejs'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; import * as commonSelectors from 'admin/tests/helpers/selectors'; module('Acceptance | accounts | update', function (hooks) { @@ -36,8 +31,8 @@ module('Acceptance | accounts | update', function (hooks) { account: null, }; - hooks.beforeEach(function () { - authenticateSession({ username: 'admin' }); + hooks.beforeEach(async function () { + await authenticateSession({ username: 'admin' }); instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', diff --git a/ui/admin/tests/acceptance/aliases/create-test.js b/ui/admin/tests/acceptance/aliases/create-test.js index 16c28b432d..f571bd9e5a 100644 --- a/ui/admin/tests/acceptance/aliases/create-test.js +++ b/ui/admin/tests/acceptance/aliases/create-test.js @@ -43,7 +43,7 @@ module('Acceptance | aliases | create', function (hooks) { newAlias: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { instances.scopes.global = this.server.create('scope', { id: 'global' }); urls.globalScope = `/scopes/global`; urls.aliases = `${urls.globalScope}/aliases`; @@ -51,7 +51,7 @@ module('Acceptance | aliases | create', function (hooks) { getAliasCount = () => this.server.schema.aliases.all().models.length; features = this.owner.lookup('service:features'); features.enable('ssh-session-recording'); - authenticateSession({ username: 'admin' }); + await authenticateSession({ username: 'admin' }); }); test('users can create a new alias with host and target info', async function (assert) { diff --git a/ui/admin/tests/acceptance/aliases/delete-test.js b/ui/admin/tests/acceptance/aliases/delete-test.js index 81885c1bbd..3c30d0296b 100644 --- a/ui/admin/tests/acceptance/aliases/delete-test.js +++ b/ui/admin/tests/acceptance/aliases/delete-test.js @@ -38,8 +38,8 @@ module('Acceptance | aliases | delete', function (hooks) { target: null, }; - hooks.beforeEach(function () { - authenticateSession({}); + hooks.beforeEach(async function () { + await authenticateSession({}); instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', diff --git a/ui/admin/tests/acceptance/aliases/list-test.js b/ui/admin/tests/acceptance/aliases/list-test.js index 222d14cc60..39701da444 100644 --- a/ui/admin/tests/acceptance/aliases/list-test.js +++ b/ui/admin/tests/acceptance/aliases/list-test.js @@ -5,7 +5,7 @@ import { module, test } from 'qunit'; import { visit, click, currentURL, waitFor, fillIn } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import { authenticateSession } from 'ember-simple-auth/test-support'; @@ -44,7 +44,7 @@ module('Acceptance | aliases | list', function (hooks) { aliasWithTarget: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', @@ -71,7 +71,7 @@ module('Acceptance | aliases | list', function (hooks) { urls.aliasWithTarget = `${urls.aliases}/${instances.aliasWithTarget.id}`; intl = this.owner.lookup('service:intl'); - authenticateSession({}); + await authenticateSession({}); }); test('users can navigate to aliases with proper authorization', async function (assert) { diff --git a/ui/admin/tests/acceptance/aliases/read-test.js b/ui/admin/tests/acceptance/aliases/read-test.js index f40e955fa3..13817733f0 100644 --- a/ui/admin/tests/acceptance/aliases/read-test.js +++ b/ui/admin/tests/acceptance/aliases/read-test.js @@ -10,13 +10,13 @@ import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { authenticateSession } from 'ember-simple-auth/test-support'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; module('Acceptance | aliases | read', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); setupIndexedDb(hooks); - const MESSAGE_SELECTOR = '.rose-message-subtitle'; const TABLE_LINK_SELECTOR = '.hds-table__tbody tr:first-child a'; const instances = { @@ -34,7 +34,7 @@ module('Acceptance | aliases | read', function (hooks) { unknownAlias: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', @@ -48,7 +48,7 @@ module('Acceptance | aliases | read', function (hooks) { urls.alias = `${urls.aliases}/${instances.alias.id}`; urls.unknownAlias = `${urls.aliases}/foo`; - authenticateSession({ username: 'admin' }); + await authenticateSession({ username: 'admin' }); }); test('visiting a alias', async function (assert) { @@ -77,6 +77,8 @@ module('Acceptance | aliases | read', function (hooks) { await visit(urls.unknownAlias); await a11yAudit(); - assert.dom(MESSAGE_SELECTOR).hasText('Error 404'); + assert + .dom(commonSelectors.RESOURCE_NOT_FOUND_SUBTITLE) + .hasText(commonSelectors.RESOURCE_NOT_FOUND_VALUE); }); }); diff --git a/ui/admin/tests/acceptance/aliases/update-test.js b/ui/admin/tests/acceptance/aliases/update-test.js index 37e49392de..2820e0c52f 100644 --- a/ui/admin/tests/acceptance/aliases/update-test.js +++ b/ui/admin/tests/acceptance/aliases/update-test.js @@ -48,7 +48,7 @@ module('Acceptance | aliases | update', function (hooks) { target: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', @@ -66,7 +66,7 @@ module('Acceptance | aliases | update', function (hooks) { urls.alias = `${urls.aliases}/${instances.alias.id}`; aliasCount = () => this.server.schema.aliases.all().models.length; - authenticateSession({}); + await authenticateSession({}); }); test('users can update an exisiting alias', async function (assert) { diff --git a/ui/admin/tests/acceptance/auth-methods/create-test.js b/ui/admin/tests/acceptance/auth-methods/create-test.js index cf7ca645d1..fe8c037823 100644 --- a/ui/admin/tests/acceptance/auth-methods/create-test.js +++ b/ui/admin/tests/acceptance/auth-methods/create-test.js @@ -5,54 +5,19 @@ import { module, test } from 'qunit'; import { visit, currentURL, click, fillIn, select } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import { Response } from 'miragejs'; -import { - authenticateSession, - // // These are left here intentionally for future reference. - // //currentSession, - // //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; +import * as selectors from './selectors'; module('Acceptance | auth-methods | create', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); setupIndexedDb(hooks); - const DROPDOWN_SELECTOR_ICON = - 'tbody .hds-table__tr:nth-child(1) .hds-table__td:last-child .hds-dropdown-toggle-icon'; - const DROPDOWN_SELECTOR_OPTION = - '.hds-dropdown__content .hds-dropdown-list-item [type=button]'; - const NEW_DROPDOWN_SELECTOR = - '[data-test-new-dropdown] .hds-dropdown-toggle-button'; - const SAVE_BTN_SELECTOR = '.rose-form-actions [type="submit"]'; - const CANCEL_BTN_SELECTOR = '.rose-form-actions [type="button"]'; - const NAME_INPUT_SELECTOR = '[name="name"]'; - const URLS_INPUT_SELECTOR = '[name="urls"]'; - const DESC_INPUT_SELECTOR = '[name="description"]'; - const ERROR_MSG_SELECTOR = - '[data-test-toast-notification] .hds-alert__description'; - const FIELD_ERROR_TEXT_SELECTOR = '.hds-form-error__message'; - - const CERTIFICATES_BTN_SELECTOR = '[name="certificates"] button'; - const CERTIFICATES_INPUT_SELECTOR = '[name="certificates"] textarea'; - const IDP_CERTS_INPUT_SELECTOR = '[name="idp_ca_certs"] textarea'; - const IDP_CERTS_BTN_SELECTOR = '[name="idp_ca_certs"] button'; - - const ALLOWED_AUDIENCES_BTN_SELECTOR = '[name="allowed_audiences"] button'; - const ALLOWED_AUDIENCES_INPUT_SELECTOR = '[name="allowed_audiences"] input'; - - const CLAIMS_SCOPES_BTN_SELECTOR = '[name="claims_scopes"] button'; - const CLAIMS_SCOPES_INPUT_SELECTOR = '[name="claims_scopes"] input'; - const TOGGLE_SELECTOR = '[name="prompts"]'; - - const MANAGE_DROPDOWN_SELECTOR = - '[data-test-manage-auth-method] button:first-child'; - const MAKE_PRIMARY_SELECTOR = - '[data-test-manage-auth-method] ul li:first-child button'; - let getAuthMethodsCount; let featuresService; @@ -74,8 +39,8 @@ module('Acceptance | auth-methods | create', function (hooks) { authMethod: null, }; - hooks.beforeEach(function () { - authenticateSession({}); + hooks.beforeEach(async function () { + await authenticateSession({}); instances.orgScope = this.server.create( 'scope', { @@ -110,9 +75,12 @@ module('Acceptance | auth-methods | create', function (hooks) { const count = getAuthMethodsCount(); await visit(urls.newAuthMethod); - await fillIn(NAME_INPUT_SELECTOR, 'AuthMethod name'); - await fillIn(DESC_INPUT_SELECTOR, 'description'); - await click(SAVE_BTN_SELECTOR); + await fillIn(commonSelectors.FIELD_NAME, commonSelectors.FIELD_NAME_VALUE); + await fillIn( + commonSelectors.FIELD_DESCRIPTION, + commonSelectors.FIELD_DESCRIPTION_VALUE, + ); + await click(commonSelectors.SAVE_BTN); assert.strictEqual(getAuthMethodsCount(), count + 1); }); @@ -120,123 +88,223 @@ module('Acceptance | auth-methods | create', function (hooks) { const count = getAuthMethodsCount(); await visit(`${urls.authMethods}/new?type=oidc`); - const name = 'oidc name'; - await fillIn(NAME_INPUT_SELECTOR, name); - await fillIn(DESC_INPUT_SELECTOR, 'description'); - await fillIn('[name="issuer"]', 'issuer'); - await fillIn('[name="client_id"]', 'client_id'); - await fillIn('[name="client_secret"]', 'client_secret'); - await select('form fieldset:nth-of-type(1) select', 'RS384'); - await click('[data-test-add-option-button]'); - await fillIn(ALLOWED_AUDIENCES_INPUT_SELECTOR, 'allowed_audiences'); - await click(ALLOWED_AUDIENCES_BTN_SELECTOR, 'allowed_audiences'); - await fillIn(CLAIMS_SCOPES_INPUT_SELECTOR, 'claims_scopes'); - await click(CLAIMS_SCOPES_BTN_SELECTOR, 'claims_scopes'); + await fillIn(commonSelectors.FIELD_NAME, commonSelectors.FIELD_NAME_VALUE); + await fillIn( + commonSelectors.FIELD_DESCRIPTION, + commonSelectors.FIELD_DESCRIPTION_VALUE, + ); + await fillIn(selectors.FIELD_ISSUER, selectors.FIELD_ISSUER_VALUE); + await fillIn(selectors.FIELD_CLIENT_ID, selectors.FIELD_CLIENT_ID_VALUE); + await fillIn( + selectors.FIELD_CLIENT_SECRET, + selectors.FIELD_CLIENT_SECRET_VALUE, + ); + await select( + selectors.FIELD_SIGNING_ALGORITHMS, + selectors.FIELD_SIGNING_ALGORITHMS_VALUE, + ); + await click(selectors.FIELD_SIGNING_ALGORITHMS_ADD_BTN); + await fillIn( + selectors.FIELD_ALLOWED_AUDIENCES, + selectors.FIELD_ALLOWED_AUDIENCES_VALUE, + ); + await click(selectors.FIELD_ALLOWED_AUDIENCES_ADD_BTN); + await fillIn( + selectors.FIELD_CLAIMS_SCOPES, + selectors.FIELD_CLAIMS_SCOPES_VALUE, + ); + await click(selectors.FIELD_CLAIMS_SCOPES_ADD_BTN); await fillIn( - '[name="account_claim_maps"] tbody td:nth-of-type(1) input', - 'from_claim', + selectors.FIELD_ACCOUNT_CLAIM_MAPS_FROM_CLAIM, + selectors.FIELD_ACCOUNT_CLAIM_MAPS_FROM_CLAIM_VALUE, ); await select( - '[name="account_claim_maps"] tbody td:nth-of-type(2) select', - 'email', + selectors.FIELD_ACCOUNT_CLAIM_MAPS_TO_CLAIM, + selectors.FIELD_ACCOUNT_CLAIM_MAPS_TO_CLAIM_VALUE, ); + await click(selectors.FIELD_ACCOUNT_CLAIM_MAPS_ADD_BTN); - await click('[name="account_claim_maps"] button'); + await fillIn(selectors.FIELD_IDP_CERTS, selectors.FIELD_IDP_CERTS_VALUE); + await click(selectors.FIELD_IDP_CERTS_ADD_BTN); + await fillIn(selectors.FIELD_MAX_AGE, selectors.FIELD_MAX_AGE_VALUE); - await fillIn(IDP_CERTS_INPUT_SELECTOR, 'certificates'); - await click(IDP_CERTS_BTN_SELECTOR); - await fillIn('[name="max_age"]', '5'); - await fillIn('[name="api_url_prefix"]', 'api_url_prefix'); - await click('[id="consent"]', 'consent'); + await fillIn( + selectors.FIELD_API_URL_PREFIX, + selectors.FIELD_API_URL_PREFIX_VALUE, + ); + await click(selectors.FIELD_PROMPTS_CONSENT); //If skip prompts toggle is clicked, then we hide the rest of the prompt options - await click(TOGGLE_SELECTOR); - assert.dom('[id="select_account"]').isNotVisible(); - await click(SAVE_BTN_SELECTOR); + await click(selectors.FIELD_PROMPTS); + assert.dom(selectors.FIELD_PROMPTS_SELECT_ACCOUNT).isNotVisible(); + await click(commonSelectors.SAVE_BTN); assert.strictEqual(getAuthMethodsCount(), count + 1); - const authMethod = this.server.schema.authMethods.findBy({ name }); - assert.strictEqual(authMethod.name, name); - assert.strictEqual(authMethod.description, 'description'); - assert.strictEqual(authMethod.attributes.issuer, 'issuer'); - assert.strictEqual(authMethod.attributes.client_id, 'client_id'); - assert.deepEqual(authMethod.attributes.signing_algorithms, ['RS384']); + const authMethod = this.server.schema.authMethods.findBy({ + name: commonSelectors.FIELD_NAME_VALUE, + }); + assert.strictEqual(authMethod.name, commonSelectors.FIELD_NAME_VALUE); + assert.strictEqual( + authMethod.description, + commonSelectors.FIELD_DESCRIPTION_VALUE, + ); + assert.strictEqual( + authMethod.attributes.issuer, + selectors.FIELD_ISSUER_VALUE, + ); + assert.strictEqual( + authMethod.attributes.client_id, + selectors.FIELD_CLIENT_ID_VALUE, + ); + assert.deepEqual(authMethod.attributes.signing_algorithms, [ + selectors.FIELD_SIGNING_ALGORITHMS_VALUE, + ]); assert.deepEqual(authMethod.attributes.allowed_audiences, [ - 'allowed_audiences', + selectors.FIELD_ALLOWED_AUDIENCES_VALUE, + ]); + assert.deepEqual(authMethod.attributes.claims_scopes, [ + selectors.FIELD_CLAIMS_SCOPES_VALUE, ]); - assert.deepEqual(authMethod.attributes.claims_scopes, ['claims_scopes']); assert.deepEqual(authMethod.attributes.account_claim_maps, [ - 'from_claim=email', + `${selectors.FIELD_ACCOUNT_CLAIM_MAPS_FROM_CLAIM_VALUE}=${selectors.FIELD_ACCOUNT_CLAIM_MAPS_TO_CLAIM_VALUE}`, ]); - assert.deepEqual(authMethod.attributes.idp_ca_certs, ['certificates']); - assert.strictEqual(authMethod.attributes.max_age, 5); - assert.strictEqual(authMethod.attributes.api_url_prefix, 'api_url_prefix'); + assert.deepEqual(authMethod.attributes.idp_ca_certs, [ + selectors.FIELD_IDP_CERTS_VALUE, + ]); + assert.strictEqual( + authMethod.attributes.max_age, + parseInt(selectors.FIELD_MAX_AGE_VALUE), + ); + assert.strictEqual( + authMethod.attributes.api_url_prefix, + selectors.FIELD_API_URL_PREFIX_VALUE, + ); assert.deepEqual(authMethod.attributes.prompts, ['none']); }); test('Users can create a new ldap auth method', async function (assert) { featuresService.enable('ldap-auth-methods'); const authMethodsCount = getAuthMethodsCount(); - const name = 'ldap auth method'; await visit(urls.authMethods); - await click(NEW_DROPDOWN_SELECTOR); - await click(`[href="${urls.newLdapAuthMethod}"]`); - await fillIn(NAME_INPUT_SELECTOR, name); - await fillIn(DESC_INPUT_SELECTOR, 'description'); - await fillIn('[name="urls"]', 'url1,url2'); - await fillIn(CERTIFICATES_INPUT_SELECTOR, 'certificate'); - await click(CERTIFICATES_BTN_SELECTOR); - await fillIn('[name="client_certificate"]', 'client cert'); - await fillIn('[name="client_certificate_key"]', 'client cert key'); - await click('[name="start_tls"]'); - await click('[name="insecure_tls"]'); - await fillIn('[name="bind_dn"]', 'bind dn'); - await fillIn('[name="bind_password"]', 'password'); - await fillIn('[name="upn_domain"]', 'upn domain'); - await click('[name="discover_dn"]'); - await click('[name="anon_group_search"]'); - await fillIn('[name="user_dn"]', 'user dn'); - await fillIn('[name="user_attr"]', 'user attr'); - await fillIn('[name="user_filter"]', 'user filter'); - await fillIn('[name="account_attribute_maps"] input', 'attribute'); - await select('[name="account_attribute_maps"] select', 'email'); - await click('[name="account_attribute_maps"] button'); - await fillIn('[name="group_dn"]', 'group dn'); - await fillIn('[name="group_attr"]', 'group attr'); - await fillIn('[name="group_filter"]', 'group filter'); - await click('[name="enable_groups"]'); - await click('[name="use_token_groups"]'); - await click(SAVE_BTN_SELECTOR); + await click(selectors.NEW_DROPDOWN); + await click(commonSelectors.HREF(urls.newLdapAuthMethod)); + await fillIn(commonSelectors.FIELD_NAME, commonSelectors.FIELD_NAME_VALUE); + await fillIn( + commonSelectors.FIELD_DESCRIPTION, + commonSelectors.FIELD_DESCRIPTION_VALUE, + ); + await fillIn(selectors.FIELD_URLS, selectors.FIELD_URLS_VALUE); + await fillIn( + selectors.FIELD_CERTIFICATES, + selectors.FIELD_CERTIFICATES_VALUE, + ); + await click(selectors.FIELD_CERTIFICATES_ADD_BTN); + await fillIn( + selectors.FIELD_CLIENT_CERTIFICATE, + selectors.FIELD_CLIENT_CERTIFICATE_VALUE, + ); + await fillIn( + selectors.FIELD_CLIENT_CERTIFICATE_KEY, + selectors.FIELD_CLIENT_CERTIFICATE_KEY_VALUE, + ); + await click(selectors.FIELD_START_TLS); + await click(selectors.FIELD_INSECURE_TLS); + await fillIn(selectors.FIELD_BIND_DN, selectors.FIELD_BIND_DN_VALUE); + await fillIn( + selectors.FIELD_BIND_PASSWORD, + selectors.FIELD_BIND_PASSWORD_VALUE, + ); + await fillIn(selectors.FIELD_UPN_DOMAIN, selectors.FIELD_UPN_DOMAIN_VALUE); + await click(selectors.FIELD_DISCOVER_DN); + await click(selectors.FIELD_ANON_GROUP_SEARCH); + await fillIn(selectors.FIELD_USER_DN, selectors.FIELD_USER_DN_VALUE); + await fillIn(selectors.FIELD_USER_ATTR, selectors.FIELD_USER_ATTR_VALUE); + await fillIn( + selectors.FIELD_USER_FILTER, + selectors.FIELD_USER_FILTER_VALUE, + ); + await fillIn( + selectors.FIELD_ACCOUNT_ATTRIBUTE_MAPS_FROM, + selectors.FIELD_ACCOUNT_ATTRIBUTE_MAPS_FROM_VALUE, + ); + await select( + selectors.FIELD_ACCOUNT_ATTRIBUTE_MAPS_TO, + selectors.FIELD_ACCOUNT_ATTRIBUTE_MAPS_TO_VALUE, + ); + await click(selectors.FIELD_ACCOUNT_ATTRIBUTE_MAPS_ADD_BTN); + await fillIn(selectors.FIELD_GROUP_DN, selectors.FIELD_GROUP_DN_VALUE); + await fillIn(selectors.FIELD_GROUP_ATTR, selectors.FIELD_GROUP_ATTR_VALUE); + await fillIn( + selectors.FIELD_GROUP_FILTER, + selectors.FIELD_GROUP_FILTER_VALUE, + ); + await click(selectors.FIELD_ENABLE_GROUPS); + await click(selectors.FIELD_USE_TOKEN_GROUPS); + await click(commonSelectors.SAVE_BTN); assert.strictEqual(getAuthMethodsCount(), authMethodsCount + 1); - const ldapAuthMethod = this.server.schema.authMethods.findBy({ name }); - assert.strictEqual(ldapAuthMethod.name, name); - assert.strictEqual(ldapAuthMethod.description, 'description'); - assert.deepEqual(ldapAuthMethod.attributes.urls, ['url1', 'url2']); - assert.deepEqual(ldapAuthMethod.attributes.certificates, ['certificate']); + const ldapAuthMethod = this.server.schema.authMethods.findBy({ + name: commonSelectors.FIELD_NAME_VALUE, + }); + assert.strictEqual(ldapAuthMethod.name, commonSelectors.FIELD_NAME_VALUE); + assert.strictEqual( + ldapAuthMethod.description, + commonSelectors.FIELD_DESCRIPTION_VALUE, + ); + assert.deepEqual( + ldapAuthMethod.attributes.urls, + selectors.FIELD_URLS_VALUE.split(','), + ); + assert.deepEqual(ldapAuthMethod.attributes.certificates, [ + selectors.FIELD_CERTIFICATES_VALUE, + ]); assert.strictEqual( ldapAuthMethod.attributes.client_certificate, - 'client cert', + selectors.FIELD_CLIENT_CERTIFICATE_VALUE, ); assert.notOk(ldapAuthMethod.attributes.client_certificate_key); assert.true(ldapAuthMethod.attributes.start_tls); assert.true(ldapAuthMethod.attributes.insecure_tls); - assert.strictEqual(ldapAuthMethod.attributes.bind_dn, 'bind dn'); + assert.strictEqual( + ldapAuthMethod.attributes.bind_dn, + selectors.FIELD_BIND_DN_VALUE, + ); assert.notOk(ldapAuthMethod.attributes.bind_password); - assert.strictEqual(ldapAuthMethod.attributes.upn_domain, 'upn domain'); + assert.strictEqual( + ldapAuthMethod.attributes.upn_domain, + selectors.FIELD_UPN_DOMAIN_VALUE, + ); assert.true(ldapAuthMethod.attributes.discover_dn); assert.true(ldapAuthMethod.attributes.anon_group_search); - assert.strictEqual(ldapAuthMethod.attributes.user_dn, 'user dn'); - assert.strictEqual(ldapAuthMethod.attributes.user_attr, 'user attr'); - assert.strictEqual(ldapAuthMethod.attributes.user_filter, 'user filter'); + assert.strictEqual( + ldapAuthMethod.attributes.user_dn, + selectors.FIELD_USER_DN_VALUE, + ); + assert.strictEqual( + ldapAuthMethod.attributes.user_attr, + selectors.FIELD_USER_ATTR_VALUE, + ); + assert.strictEqual( + ldapAuthMethod.attributes.user_filter, + selectors.FIELD_USER_FILTER_VALUE, + ); assert.deepEqual(ldapAuthMethod.attributes.account_attribute_maps, [ - 'attribute=email', + `${selectors.FIELD_ACCOUNT_ATTRIBUTE_MAPS_FROM_VALUE}=${selectors.FIELD_ACCOUNT_ATTRIBUTE_MAPS_TO_VALUE}`, ]); - assert.strictEqual(ldapAuthMethod.attributes.group_dn, 'group dn'); - assert.strictEqual(ldapAuthMethod.attributes.group_attr, 'group attr'); - assert.strictEqual(ldapAuthMethod.attributes.group_filter, 'group filter'); + assert.strictEqual( + ldapAuthMethod.attributes.group_dn, + selectors.FIELD_GROUP_DN_VALUE, + ); + assert.strictEqual( + ldapAuthMethod.attributes.group_attr, + selectors.FIELD_GROUP_ATTR_VALUE, + ); + assert.strictEqual( + ldapAuthMethod.attributes.group_filter, + selectors.FIELD_GROUP_FILTER_VALUE, + ); assert.true(ldapAuthMethod.attributes.enable_groups); assert.true(ldapAuthMethod.attributes.use_token_groups); }); @@ -248,7 +316,7 @@ module('Acceptance | auth-methods | create', function (hooks) { ]; await visit(urls.orgScope); - await click(`[href="${urls.authMethods}"]`); + await click(commonSelectors.HREF(urls.authMethods)); assert.true( instances.orgScope.authorized_collection_actions['auth-methods'].includes( @@ -256,23 +324,22 @@ module('Acceptance | auth-methods | create', function (hooks) { ), ); - await click(NEW_DROPDOWN_SELECTOR); - - assert.dom(`[href="${urls.newAuthMethod}"]`).exists(); + await click(selectors.NEW_DROPDOWN); + assert.dom(commonSelectors.HREF(urls.newAuthMethod)).isVisible(); }); test('Users cannot navigate to new auth-methods route without proper authorization', async function (assert) { instances.orgScope.authorized_collection_actions['auth-methods'] = ['list']; await visit(urls.orgScope); - await click(`[href="${urls.authMethods}"]`); + await click(commonSelectors.HREF(urls.authMethods)); assert.false( instances.orgScope.authorized_collection_actions['auth-methods'].includes( 'create', ), ); - assert.dom(NEW_DROPDOWN_SELECTOR).doesNotExist(); + assert.dom(selectors.NEW_DROPDOWN).doesNotExist(); }); test('Users can navigate to new ldap auth-method route with proper authorization and feature flag enabled', async function (assert) { @@ -283,7 +350,7 @@ module('Acceptance | auth-methods | create', function (hooks) { featuresService.enable('ldap-auth-methods'); await visit(urls.orgScope); - await click(`[href="${urls.authMethods}"]`); + await click(commonSelectors.HREF(urls.authMethods)); assert.true( instances.orgScope.authorized_collection_actions['auth-methods'].includes( @@ -291,9 +358,8 @@ module('Acceptance | auth-methods | create', function (hooks) { ), ); - await click(NEW_DROPDOWN_SELECTOR); - - assert.dom(`[href="${urls.newLdapAuthMethod}"]`).exists(); + await click(selectors.NEW_DROPDOWN); + assert.dom(commonSelectors.HREF(urls.newLdapAuthMethod)).isVisible(); }); test('Users cannot navigate to new ldap auth-method route when feature flag disabled', async function (assert) { @@ -303,7 +369,7 @@ module('Acceptance | auth-methods | create', function (hooks) { ]; await visit(urls.orgScope); - await click(`[href="${urls.authMethods}"]`); + await click(commonSelectors.HREF(urls.authMethods)); assert.true( instances.orgScope.authorized_collection_actions['auth-methods'].includes( @@ -311,20 +377,22 @@ module('Acceptance | auth-methods | create', function (hooks) { ), ); - await click(NEW_DROPDOWN_SELECTOR); - - assert.dom(`[href="${urls.newLdapAuthMethod}"]`).doesNotExist(); + await click(selectors.NEW_DROPDOWN); + assert.dom(commonSelectors.HREF(urls.newLdapAuthMethod)).doesNotExist(); }); test('can cancel new auth method creation', async function (assert) { const count = getAuthMethodsCount(); await visit(urls.authMethods); - await click(NEW_DROPDOWN_SELECTOR); - await click(`[href="${urls.newAuthMethod}"]`); - await fillIn(NAME_INPUT_SELECTOR, 'AuthMethod name'); - await fillIn(DESC_INPUT_SELECTOR, 'description'); - await click(CANCEL_BTN_SELECTOR); + await click(selectors.NEW_DROPDOWN); + await click(commonSelectors.HREF(urls.newAuthMethod)); + await fillIn(commonSelectors.FIELD_NAME, commonSelectors.FIELD_NAME_VALUE); + await fillIn( + commonSelectors.FIELD_DESCRIPTION, + commonSelectors.FIELD_DESCRIPTION_VALUE, + ); + await click(commonSelectors.CANCEL_BTN); assert.strictEqual(getAuthMethodsCount(), count); assert.strictEqual(currentURL(), urls.authMethods); @@ -337,9 +405,9 @@ module('Acceptance | auth-methods | create', function (hooks) { ); await visit(urls.authMethods); - await click(`[href="${urls.authMethod}"]`); - await click(MANAGE_DROPDOWN_SELECTOR); - await click(MAKE_PRIMARY_SELECTOR); + await click(commonSelectors.HREF(urls.authMethod)); + await click(selectors.MANAGE_DROPDOWN); + await click(selectors.MANAGE_DROPDOWN_MAKE_PRIMARY); const scope = this.server.schema.scopes.find(instances.orgScope.id); assert.strictEqual( @@ -365,10 +433,12 @@ module('Acceptance | auth-methods | create', function (hooks) { instances.orgScope.primaryAuthMethodId, 'Primary auth method is not yet set.', ); + await visit(urls.authMethod); - await click(MANAGE_DROPDOWN_SELECTOR); - await click(MAKE_PRIMARY_SELECTOR); - assert.dom(ERROR_MSG_SELECTOR).exists(); + await click(selectors.MANAGE_DROPDOWN); + await click(selectors.MANAGE_DROPDOWN_MAKE_PRIMARY); + + assert.dom(commonSelectors.ALERT_TOAST).isVisible(); }); test('user can remove as primary an auth method', async function (assert) { @@ -381,9 +451,9 @@ module('Acceptance | auth-methods | create', function (hooks) { ); await visit(urls.authMethods); - await click(`[href="${urls.authMethod}"]`); - await click(MANAGE_DROPDOWN_SELECTOR); - await click(MAKE_PRIMARY_SELECTOR); + await click(commonSelectors.HREF(urls.authMethod)); + await click(selectors.MANAGE_DROPDOWN); + await click(selectors.MANAGE_DROPDOWN_MAKE_PRIMARY); const scope = this.server.schema.scopes.find(instances.orgScope.id); assert.notOk(scope.primaryAuthMethodId, 'Primary auth method is unset.'); }); @@ -410,11 +480,11 @@ module('Acceptance | auth-methods | create', function (hooks) { ); await visit(urls.authMethods); - await click(`[href="${urls.authMethod}"]`); - await click(MANAGE_DROPDOWN_SELECTOR); - await click(MAKE_PRIMARY_SELECTOR); + await click(commonSelectors.HREF(urls.authMethod)); + await click(selectors.MANAGE_DROPDOWN); + await click(selectors.MANAGE_DROPDOWN_MAKE_PRIMARY); - assert.dom(ERROR_MSG_SELECTOR).hasText('Sorry!'); + assert.dom(commonSelectors.ALERT_TOAST_BODY).hasText('Sorry!'); }); test('user can make and remove primary auth methods from index', async function (assert) { @@ -424,9 +494,9 @@ module('Acceptance | auth-methods | create', function (hooks) { ); await visit(urls.authMethods); - await click(DROPDOWN_SELECTOR_ICON); - assert.dom(DROPDOWN_SELECTOR_OPTION).exists(); - await click(DROPDOWN_SELECTOR_OPTION); + await click(selectors.TABLE_FIRST_ROW_ACTION_DROPDOWN); + assert.dom(selectors.TABLE_FIRST_ROW_ACTION_FIRST_ITEM).isVisible(); + await click(selectors.TABLE_FIRST_ROW_ACTION_FIRST_ITEM); let scope = this.server.schema.scopes.find(instances.orgScope.id); @@ -435,9 +505,9 @@ module('Acceptance | auth-methods | create', function (hooks) { instances.authMethod.id, 'Primary auth method is set.', ); - await click(DROPDOWN_SELECTOR_ICON); - await click(DROPDOWN_SELECTOR_OPTION); + await click(selectors.TABLE_FIRST_ROW_ACTION_DROPDOWN); + await click(selectors.TABLE_FIRST_ROW_ACTION_FIRST_ITEM); scope = this.server.schema.scopes.find(instances.orgScope.id); assert.notOk(scope.primaryAuthMethodId, 'Primary auth method is unset.'); @@ -466,15 +536,15 @@ module('Acceptance | auth-methods | create', function (hooks) { }); await visit(urls.authMethods); - await click(NEW_DROPDOWN_SELECTOR); - await click(`[href="${urls.newLdapAuthMethod}"]`); - await fillIn(URLS_INPUT_SELECTOR, ''); - await click(SAVE_BTN_SELECTOR); + await click(selectors.NEW_DROPDOWN); + await click(commonSelectors.HREF(urls.newLdapAuthMethod)); + await fillIn(selectors.FIELD_URLS, ''); + await click(commonSelectors.SAVE_BTN); - assert.dom(ERROR_MSG_SELECTOR).hasText('The request was invalid.'); assert - .dom(FIELD_ERROR_TEXT_SELECTOR) - .hasText('At least one URL is required.'); + .dom(commonSelectors.ALERT_TOAST_BODY) + .hasText('The request was invalid.'); + assert.dom(selectors.FIELD_ERROR).hasText('At least one URL is required.'); }); test('users cannot directly navigate to new auth method route without proper authorization', async function (assert) { diff --git a/ui/admin/tests/acceptance/auth-methods/delete-test.js b/ui/admin/tests/acceptance/auth-methods/delete-test.js index b15b4f9afc..b4b92b6789 100644 --- a/ui/admin/tests/acceptance/auth-methods/delete-test.js +++ b/ui/admin/tests/acceptance/auth-methods/delete-test.js @@ -5,18 +5,14 @@ import { module, test } from 'qunit'; import { visit, click, currentURL } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { Response } from 'miragejs'; -import { - authenticateSession, - // // These are left here intentionally for future reference. - // //currentSession, - // //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; import { TYPE_AUTH_METHOD_LDAP } from 'api/models/auth-method'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; module('Acceptance | auth-methods | delete', function (hooks) { setupApplicationTest(hooks); @@ -26,8 +22,6 @@ module('Acceptance | auth-methods | delete', function (hooks) { let featuresService; let getAuthMethodCount; - const DIALOG_DELETE_BTN_SELECTOR = '.rose-dialog .rose-button-primary'; - const DIALOG_CANCEL_BTN_SELECTOR = '.rose-dialog .rose-button-secondary'; const ERROR_MSG_SELECTOR = '[data-test-toast-notification] .hds-alert__description'; @@ -51,9 +45,9 @@ module('Acceptance | auth-methods | delete', function (hooks) { ldapAuthMethod: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Setup Mirage mock resources for this test - authenticateSession({ username: 'admin' }); + await authenticateSession({ username: 'admin' }); instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', @@ -181,7 +175,7 @@ module('Acceptance | auth-methods | delete', function (hooks) { await click(`[href="${urls.authMethod}"]`); await click(MANAGE_DROPDOWN_SELECTOR); await click(DELETE_ACTION_SELECTOR); - await click(DIALOG_DELETE_BTN_SELECTOR); + await click(commonSelectors.MODAL_WARNING_CONFIRM_BTN); assert.strictEqual(currentURL(), urls.authMethods); assert.strictEqual(getAuthMethodCount(), authMethodCount - 1); @@ -197,7 +191,7 @@ module('Acceptance | auth-methods | delete', function (hooks) { await click(`[href="${urls.ldapAuthMethod}"]`); await click(MANAGE_DROPDOWN_SELECTOR); await click(DELETE_ACTION_SELECTOR); - await click(DIALOG_DELETE_BTN_SELECTOR); + await click(commonSelectors.MODAL_WARNING_CONFIRM_BTN); assert.strictEqual(currentURL(), urls.authMethods); assert.strictEqual(getAuthMethodCount(), authMethodCount - 1); @@ -212,7 +206,7 @@ module('Acceptance | auth-methods | delete', function (hooks) { await click(`[href="${urls.authMethod}"]`); await click(MANAGE_DROPDOWN_SELECTOR); await click(DELETE_ACTION_SELECTOR); - await click(DIALOG_CANCEL_BTN_SELECTOR); + await click(commonSelectors.MODAL_WARNING_CANCEL_BTN); assert.strictEqual(currentURL(), urls.authMethod); assert.strictEqual(getAuthMethodCount(), authMethodCount); @@ -228,7 +222,7 @@ module('Acceptance | auth-methods | delete', function (hooks) { await click(`[href="${urls.ldapAuthMethod}"]`); await click(MANAGE_DROPDOWN_SELECTOR); await click(DELETE_ACTION_SELECTOR); - await click(DIALOG_CANCEL_BTN_SELECTOR); + await click(commonSelectors.MODAL_WARNING_CANCEL_BTN); assert.strictEqual(currentURL(), urls.ldapAuthMethod); assert.strictEqual(getAuthMethodCount(), authMethodCount); diff --git a/ui/admin/tests/acceptance/auth-methods/list-test.js b/ui/admin/tests/acceptance/auth-methods/list-test.js index 9984296e61..6ddc05a5e9 100644 --- a/ui/admin/tests/acceptance/auth-methods/list-test.js +++ b/ui/admin/tests/acceptance/auth-methods/list-test.js @@ -5,35 +5,22 @@ import { module, test } from 'qunit'; import { visit, click, waitFor, fillIn } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; import { TYPE_AUTH_METHOD_PASSWORD, TYPE_AUTH_METHOD_OIDC, } from 'api/models/auth-method'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; +import * as selectors from './selectors'; module('Acceptance | auth-methods | list', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); setupIndexedDb(hooks); - const SEARCH_INPUT_SELECTOR = '.search-filtering [type="search"]'; - const NO_RESULTS_MSG_SELECTOR = '[data-test-no-auth-method-results]'; - const FILTER_DROPDOWN_SELECTOR = (name) => - `.search-filtering [name="${name}"] button`; - const FILTER_APPLY_BUTTON_SELECTOR = - '.search-filtering [data-test-dropdown-apply-button]'; - const AUTH_ACTIONS_SELECTOR = (id) => - `tbody [data-test-auth-methods-table-row="${id}"] .hds-table__td:last-child .hds-dropdown button`; - const MAKE_PRIMARY_ACTION_SELECTOR = '[data-test-make-primary-action]'; - const instances = { scopes: { global: null, @@ -51,7 +38,7 @@ module('Acceptance | auth-methods | list', function (hooks) { oidcAuthMethod: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create( 'scope', @@ -75,34 +62,34 @@ module('Acceptance | auth-methods | list', function (hooks) { urls.passwordAuthMethod = `${urls.authMethods}/${instances.passwordAuthMethod.id}`; urls.oidcAuthMethod = `${urls.authMethods}/${instances.oidcAuthMethod.id}`; - authenticateSession({}); + await authenticateSession({}); }); test('users can navigate to auth methods with proper authorization', async function (assert) { await visit(urls.globalScope); - await click(`[href="${urls.orgScope}"]`); + await click(commonSelectors.HREF(urls.orgScope)); assert.true( instances.scopes.org.authorized_collection_actions[ 'auth-methods' ].includes('list'), ); - assert.dom(`[href="${urls.authMethods}"]`).exists(); + assert.dom(commonSelectors.HREF(urls.authMethods)).isVisible(); }); test('users cannot navigate to index without either list or create actions', async function (assert) { instances.scopes.org.authorized_collection_actions['auth-methods'] = []; await visit(urls.globalScope); - await click(`[href="${urls.orgScope}"]`); + await click(commonSelectors.HREF(urls.orgScope)); assert.false( instances.scopes.org.authorized_collection_actions[ 'auth-methods' ].includes('list'), ); - assert.dom(`[href="${urls.authMethods}"]`).doesNotExist(); + assert.dom(commonSelectors.HREF(urls.authMethods)).isNotVisible(); }); test('users can navigate to index with only create action', async function (assert) { @@ -111,73 +98,79 @@ module('Acceptance | auth-methods | list', function (hooks) { ]; await visit(urls.globalScope); - await click(`[href="${urls.orgScope}"]`); - - assert.dom(`[href="${urls.authMethods}"]`).exists(); + await click(commonSelectors.HREF(urls.orgScope)); + assert.dom(commonSelectors.HREF(urls.authMethods)).isVisible(); }); test('user can search for a specifc auth-method by id', async function (assert) { await visit(urls.orgScope); - await click(`[href="${urls.authMethods}"]`); + await click(commonSelectors.HREF(urls.authMethods)); + assert.dom(commonSelectors.HREF(urls.passwordAuthMethod)).isVisible(); + assert.dom(commonSelectors.HREF(urls.oidcAuthMethod)).isVisible(); - assert.dom(`[href="${urls.passwordAuthMethod}"]`).exists(); - assert.dom(`[href="${urls.oidcAuthMethod}"]`).exists(); + await fillIn(selectors.SEARCH_BAR_INPUT, instances.passwordAuthMethod.id); + await waitFor(commonSelectors.HREF(urls.oidcAuthMethod), { count: 0 }); - await fillIn(SEARCH_INPUT_SELECTOR, instances.passwordAuthMethod.id); - await waitFor(`[href="${urls.oidcAuthMethod}"]`, { count: 0 }); - - assert.dom(`[href="${urls.passwordAuthMethod}"]`).exists(); - assert.dom(`[href="${urls.oidcAuthMethod}"]`).doesNotExist(); + assert.dom(commonSelectors.HREF(urls.passwordAuthMethod)).isVisible(); + assert.dom(commonSelectors.HREF(urls.oidcAuthMethod)).isNotVisible(); }); test('user can search for auth-methods and get no results', async function (assert) { await visit(urls.orgScope); - await click(`[href="${urls.authMethods}"]`); - - assert.dom(`[href="${urls.passwordAuthMethod}"]`).exists(); - assert.dom(`[href="${urls.oidcAuthMethod}"]`).exists(); + await click(commonSelectors.HREF(urls.authMethods)); + assert.dom(commonSelectors.HREF(urls.passwordAuthMethod)).isVisible(); + assert.dom(commonSelectors.HREF(urls.oidcAuthMethod)).isVisible(); - await fillIn(SEARCH_INPUT_SELECTOR, 'fake target that does not exist'); - await waitFor(NO_RESULTS_MSG_SELECTOR, { count: 1 }); + await fillIn(selectors.SEARCH_BAR_INPUT, 'fake target that does not exist'); + await waitFor(commonSelectors.PAGE_MESSAGE_HEADER, { count: 1 }); - assert.dom(`[href="${urls.passwordAuthMethod}"]`).doesNotExist(); - assert.dom(`[href="${urls.oidcAuthMethod}"]`).doesNotExist(); - assert.dom(NO_RESULTS_MSG_SELECTOR).includesText('No results found'); + assert.dom(commonSelectors.HREF(urls.passwordAuthMethod)).isNotVisible(); + assert.dom(commonSelectors.HREF(urls.oidcAuthMethod)).isNotVisible(); + assert + .dom(commonSelectors.PAGE_MESSAGE_HEADER) + .includesText('No results found'); }); test('user can filter for auth-methods by type', async function (assert) { await visit(urls.orgScope); - await click(`[href="${urls.authMethods}"]`); + await click(commonSelectors.HREF(urls.authMethods)); + assert.dom(commonSelectors.HREF(urls.passwordAuthMethod)).isVisible(); + assert.dom(commonSelectors.HREF(urls.oidcAuthMethod)).isVisible(); - assert.dom(`[href="${urls.passwordAuthMethod}"]`).exists(); - assert.dom(`[href="${urls.oidcAuthMethod}"]`).exists(); - - await click(FILTER_DROPDOWN_SELECTOR('type')); - await click(`input[value="${TYPE_AUTH_METHOD_PASSWORD}"]`); - await click(FILTER_APPLY_BUTTON_SELECTOR); + await click(commonSelectors.FILTER_DROPDOWN('type')); + await click( + commonSelectors.FILTER_DROPDOWN_ITEM(TYPE_AUTH_METHOD_PASSWORD), + ); + await click(commonSelectors.FILTER_DROPDOWN_ITEM_APPLY_BTN('type')); - assert.dom(`[href="${urls.oidcAuthMethod}"]`).doesNotExist(); - assert.dom(`[href="${urls.passwordAuthMethod}"]`).exists(); + assert.dom(commonSelectors.HREF(urls.oidcAuthMethod)).isNotVisible(); + assert.dom(commonSelectors.HREF(urls.passwordAuthMethod)).isVisible(); }); test('user can filter for auth-methods by primary', async function (assert) { await visit(urls.orgScope); - await click(`[href="${urls.authMethods}"]`); + await click(commonSelectors.HREF(urls.authMethods)); + assert.dom(commonSelectors.HREF(urls.passwordAuthMethod)).isVisible(); + assert.dom(commonSelectors.HREF(urls.oidcAuthMethod)).isVisible(); - assert.dom(`[href="${urls.passwordAuthMethod}"]`).exists(); - assert.dom(`[href="${urls.oidcAuthMethod}"]`).exists(); + await click( + selectors.TABLE_ACTION_DROPDOWN(instances.passwordAuthMethod.id), + ); + await click( + selectors.TABLE_ACTION_DROPDOWN_MAKE_PRIMARY( + instances.passwordAuthMethod.id, + ), + ); - await click(AUTH_ACTIONS_SELECTOR(instances.passwordAuthMethod.id)); - await click(MAKE_PRIMARY_ACTION_SELECTOR); - await click(FILTER_DROPDOWN_SELECTOR('primary')); - await click(`input[value="true"]`); - await click(FILTER_APPLY_BUTTON_SELECTOR); + await click(commonSelectors.FILTER_DROPDOWN('primary')); + await click(commonSelectors.FILTER_DROPDOWN_ITEM('true')); + await click(commonSelectors.FILTER_DROPDOWN_ITEM_APPLY_BTN('primary')); - assert.dom(`[href="${urls.oidcAuthMethod}"]`).doesNotExist(); - assert.dom(`[href="${urls.passwordAuthMethod}"]`).exists(); + assert.dom(commonSelectors.HREF(urls.oidcAuthMethod)).isNotVisible(); + assert.dom(commonSelectors.HREF(urls.passwordAuthMethod)).isVisible(); }); }); diff --git a/ui/admin/tests/acceptance/auth-methods/oidc-test.js b/ui/admin/tests/acceptance/auth-methods/oidc-test.js index 48792c0a76..43bf98e1dd 100644 --- a/ui/admin/tests/acceptance/auth-methods/oidc-test.js +++ b/ui/admin/tests/acceptance/auth-methods/oidc-test.js @@ -4,24 +4,12 @@ */ import { module, test } from 'qunit'; -import { - visit, - currentURL, - click, - // fillIn, - find, - // findAll, -} from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { visit, currentURL, click, find } from '@ember/test-helpers'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; //import { Response } from 'miragejs'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; import { TYPE_AUTH_METHOD_OIDC } from 'api/models/auth-method'; @@ -47,8 +35,8 @@ module('Acceptance | auth-methods | oidc', function (hooks) { authMethod: null, }; - hooks.beforeEach(function () { - authenticateSession({ username: 'admin' }); + hooks.beforeEach(async function () { + await authenticateSession({ username: 'admin' }); instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', diff --git a/ui/admin/tests/acceptance/auth-methods/read-test.js b/ui/admin/tests/acceptance/auth-methods/read-test.js index 338d9cd61c..0b3fd07388 100644 --- a/ui/admin/tests/acceptance/auth-methods/read-test.js +++ b/ui/admin/tests/acceptance/auth-methods/read-test.js @@ -5,7 +5,7 @@ import { module, test } from 'qunit'; import { visit, currentURL, click } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; @@ -47,9 +47,9 @@ module('Acceptance | auth-methods | read', function (hooks) { oidcAuthMethodGlobal: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Setup Mirage mock resources for this test - authenticateSession({ username: 'admin' }); + await authenticateSession({ username: 'admin' }); instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', diff --git a/ui/admin/tests/acceptance/auth-methods/selectors.js b/ui/admin/tests/acceptance/auth-methods/selectors.js new file mode 100644 index 0000000000..2c4be3fd22 --- /dev/null +++ b/ui/admin/tests/acceptance/auth-methods/selectors.js @@ -0,0 +1,99 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +export const FIELD_ISSUER = '[name=issuer]'; +export const FIELD_ISSUER_VALUE = 'Issuer fake value'; +export const FIELD_CLIENT_ID = '[name=client_id]'; +export const FIELD_CLIENT_ID_VALUE = 'Client id value'; +export const FIELD_CLIENT_SECRET = '[name=client_secret]'; +export const FIELD_CLIENT_SECRET_VALUE = 'Client secret value'; +export const FIELD_SIGNING_ALGORITHMS = '[name=signing_algorithms] select'; +export const FIELD_SIGNING_ALGORITHMS_VALUE = 'RS384'; +export const FIELD_SIGNING_ALGORITHMS_ADD_BTN = '[data-test-add-option-button]'; +export const FIELD_ALLOWED_AUDIENCES = '[name=allowed_audiences] input'; +export const FIELD_ALLOWED_AUDIENCES_VALUE = 'Allowed audiences'; +export const FIELD_ALLOWED_AUDIENCES_ADD_BTN = + '[name=allowed_audiences] button'; +export const FIELD_CLAIMS_SCOPES = '[name=claims_scopes] input'; +export const FIELD_CLAIMS_SCOPES_VALUE = 'Claim scopes'; +export const FIELD_CLAIMS_SCOPES_ADD_BTN = '[name=claims_scopes] button'; +export const FIELD_ACCOUNT_CLAIM_MAPS_FROM_CLAIM = + '[name=account_claim_maps] tbody td:nth-of-type(1) input'; +export const FIELD_ACCOUNT_CLAIM_MAPS_FROM_CLAIM_VALUE = 'from_claim'; +export const FIELD_ACCOUNT_CLAIM_MAPS_TO_CLAIM = + '[name=account_claim_maps] tbody td:nth-of-type(2) select'; +export const FIELD_ACCOUNT_CLAIM_MAPS_TO_CLAIM_VALUE = 'email'; +export const FIELD_ACCOUNT_CLAIM_MAPS_ADD_BTN = + '[name=account_claim_maps] button'; +export const FIELD_IDP_CERTS = '[name=idp_ca_certs] textarea'; +export const FIELD_IDP_CERTS_VALUE = 'IDP certificates'; +export const FIELD_IDP_CERTS_ADD_BTN = '[name=idp_ca_certs] button'; +export const FIELD_MAX_AGE = '[name=max_age]'; +export const FIELD_MAX_AGE_VALUE = '5'; +export const FIELD_API_URL_PREFIX = '[name=api_url_prefix]'; +export const FIELD_API_URL_PREFIX_VALUE = 'Api url prefix'; +export const FIELD_PROMPTS = '[name=prompts]'; +export const FIELD_PROMPTS_CONSENT = '[id=consent]'; +export const FIELD_PROMPTS_SELECT_ACCOUNT = '[id=select_account]'; +export const NEW_DROPDOWN = '[data-test-new-dropdown] button'; +export const FIELD_URLS = '[name=urls]'; +export const FIELD_URLS_VALUE = 'url1,url2'; +export const FIELD_CERTIFICATES = '[name=certificates] textarea'; +export const FIELD_CERTIFICATES_VALUE = 'certificate'; +export const FIELD_CERTIFICATES_ADD_BTN = '[name=certificates] button'; +export const FIELD_CLIENT_CERTIFICATE = '[name=client_certificate]'; +export const FIELD_CLIENT_CERTIFICATE_VALUE = 'Client certificate value'; +export const FIELD_CLIENT_CERTIFICATE_KEY = '[name=client_certificate_key]'; +export const FIELD_CLIENT_CERTIFICATE_KEY_VALUE = + 'Client certificate key value'; +export const FIELD_START_TLS = '[name=start_tls]'; +export const FIELD_INSECURE_TLS = '[name=insecure_tls]'; +export const FIELD_BIND_DN = '[name=bind_dn]'; +export const FIELD_BIND_DN_VALUE = 'Bind dn'; +export const FIELD_BIND_PASSWORD = '[name=bind_password]'; +export const FIELD_BIND_PASSWORD_VALUE = 'Bind password value'; +export const FIELD_UPN_DOMAIN = '[name=upn_domain]'; +export const FIELD_UPN_DOMAIN_VALUE = 'Upn domain value'; +export const FIELD_DISCOVER_DN = '[name=discover_dn]'; +export const FIELD_ANON_GROUP_SEARCH = '[name=anon_group_search]'; +export const FIELD_USER_DN = '[name=user_dn]'; +export const FIELD_USER_DN_VALUE = 'User dn value'; +export const FIELD_USER_ATTR = '[name=user_attr]'; +export const FIELD_USER_ATTR_VALUE = 'User attribute value'; +export const FIELD_USER_FILTER = '[name=user_filter]'; +export const FIELD_USER_FILTER_VALUE = 'User filter value'; +export const FIELD_ACCOUNT_ATTRIBUTE_MAPS_FROM = + '[name=account_attribute_maps] input'; +export const FIELD_ACCOUNT_ATTRIBUTE_MAPS_FROM_VALUE = 'attribute'; +export const FIELD_ACCOUNT_ATTRIBUTE_MAPS_TO = + '[name=account_attribute_maps] select'; +export const FIELD_ACCOUNT_ATTRIBUTE_MAPS_TO_VALUE = 'email'; +export const FIELD_ACCOUNT_ATTRIBUTE_MAPS_ADD_BTN = + '[name=account_attribute_maps] button'; +export const FIELD_GROUP_DN = '[name=group_dn]'; +export const FIELD_GROUP_DN_VALUE = 'Group dn value'; +export const FIELD_GROUP_ATTR = '[name=group_attr]'; +export const FIELD_GROUP_ATTR_VALUE = 'Group attribute value'; +export const FIELD_GROUP_FILTER = '[name=group_filter]'; +export const FIELD_GROUP_FILTER_VALUE = 'Group filter value'; +export const FIELD_ENABLE_GROUPS = '[name=enable_groups]'; +export const FIELD_USE_TOKEN_GROUPS = '[name=use_token_groups]'; +export const FIELD_ERROR = '.hds-form-error__message'; // Selects any error message on the DOM. + +export const MANAGE_DROPDOWN = '[data-test-manage-auth-method] button'; +export const MANAGE_DROPDOWN_MAKE_PRIMARY = + '[data-test-manage-auth-method] ul li:first-child button'; + +export const TABLE_FIRST_ROW_ACTION_DROPDOWN = + 'tbody tr:first-child td:last-child button'; +export const TABLE_FIRST_ROW_ACTION_FIRST_ITEM = + 'tbody tr:first-child td:last-child ul li:first-child button'; + +export const SEARCH_BAR_INPUT = '[type=search]'; + +export const TABLE_ACTION_DROPDOWN = (authMethodId) => + `tbody [data-test-auth-methods-table-row="${authMethodId}"] .hds-table__td:last-child .hds-dropdown button`; +export const TABLE_ACTION_DROPDOWN_MAKE_PRIMARY = (authMethodId) => + `tbody tr[data-test-auth-methods-table-row="${authMethodId}"] td:last-child ul li:first-child button`; diff --git a/ui/admin/tests/acceptance/auth-methods/update-test.js b/ui/admin/tests/acceptance/auth-methods/update-test.js index d8acd06703..6458bb141b 100644 --- a/ui/admin/tests/acceptance/auth-methods/update-test.js +++ b/ui/admin/tests/acceptance/auth-methods/update-test.js @@ -5,17 +5,12 @@ import { module, test } from 'qunit'; import { visit, click, fillIn, select, findAll } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { Response } from 'miragejs'; -import { - authenticateSession, - // // These are left here intentionally for future reference. - // //currentSession, - // //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; import { TYPE_AUTH_METHOD_OIDC, TYPE_AUTH_METHOD_LDAP, @@ -78,9 +73,9 @@ module('Acceptance | auth-methods | update', function (hooks) { ldapAuthMethod: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Setup Mirage mock resources for this test - authenticateSession({ username: 'admin' }); + await authenticateSession({ username: 'admin' }); instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', diff --git a/ui/admin/tests/acceptance/authentication-test.js b/ui/admin/tests/acceptance/authentication-test.js index d32d1abee3..949f9e4158 100644 --- a/ui/admin/tests/acceptance/authentication-test.js +++ b/ui/admin/tests/acceptance/authentication-test.js @@ -12,7 +12,7 @@ import { findAll, getRootElement, } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import { Response } from 'miragejs'; @@ -66,8 +66,8 @@ module('Acceptance | authentication', function (hooks) { let sessionsURL; let targetsURL; - hooks.beforeEach(function () { - invalidateSession(); + hooks.beforeEach(async function () { + await invalidateSession(); indexURL = '/'; globalScope = this.server.create('scope', { id: 'global' }); orgScope1 = this.server.create( @@ -235,7 +235,7 @@ module('Acceptance | authentication', function (hooks) { }); test('visiting any authentication parent route while already authenticated with an org redirects to projects', async function (assert) { - authenticateSession({ scope }); + await authenticateSession({ scope }); await visit(indexURL); assert.strictEqual(currentURL(), projectsURL); await visit(scopesURL); @@ -250,7 +250,7 @@ module('Acceptance | authentication', function (hooks) { }); test('visiting index or scopes routes while already authenticated with global redirects to orgs', async function (assert) { - authenticateSession({ + await authenticateSession({ scope: { id: globalScope.id, type: globalScope.type }, }); await visit(indexURL); @@ -316,7 +316,7 @@ module('Acceptance | authentication', function (hooks) { }); test('401 responses result in deauthentication', async function (assert) { - authenticateSession({ + await authenticateSession({ scope: { id: globalScope.id, type: globalScope.type }, }); await visit(orgsURL); @@ -333,26 +333,36 @@ module('Acceptance | authentication', function (hooks) { }); test('color theme is applied from session data', async function (assert) { - authenticateSession({ + await authenticateSession({ scope: { id: globalScope.id, type: globalScope.type }, }); - // system default await visit(orgsURL); - assert.notOk(currentSession().get('data.theme')); - assert.notOk(getRootElement().classList.contains('rose-theme-light')); - assert.notOk(getRootElement().classList.contains('rose-theme-dark')); - // toggle light mode - await click('[name="theme"][value="light"]'); + + // light mode assert.strictEqual(currentSession().get('data.theme'), 'light'); assert.ok(getRootElement().classList.contains('rose-theme-light')); assert.notOk(getRootElement().classList.contains('rose-theme-dark')); + + // toggle system default + await click('[name="theme"][value="system-default-theme"]'); + + assert.strictEqual( + currentSession().get('data.theme'), + 'system-default-theme', + ); + assert.notOk(getRootElement().classList.contains('rose-theme-light')); + assert.notOk(getRootElement().classList.contains('rose-theme-dark')); + // toggle dark mode await click('[name="theme"][value="dark"]'); + assert.strictEqual(currentSession().get('data.theme'), 'dark'); assert.notOk(getRootElement().classList.contains('rose-theme-light')); assert.ok(getRootElement().classList.contains('rose-theme-dark')); + // toggle system default await click('[name="theme"][value="system-default-theme"]'); + assert.strictEqual( currentSession().get('data.theme'), 'system-default-theme', diff --git a/ui/admin/tests/acceptance/credential-library/create-test.js b/ui/admin/tests/acceptance/credential-library/create-test.js index bea22e8958..3604fb4f7f 100644 --- a/ui/admin/tests/acceptance/credential-library/create-test.js +++ b/ui/admin/tests/acceptance/credential-library/create-test.js @@ -5,7 +5,7 @@ import { module, test } from 'qunit'; import { visit, click, fillIn, currentURL, select } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { authenticateSession } from 'ember-simple-auth/test-support'; @@ -40,7 +40,7 @@ module('Acceptance | credential-libraries | create', function (hooks) { newCredentialLibrary: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { @@ -71,7 +71,7 @@ module('Acceptance | credential-libraries | create', function (hooks) { // Generate resource counter getCredentialLibraryCount = () => this.server.schema.credentialLibraries.all().models.length; - authenticateSession({ username: 'admin' }); + await authenticateSession({ username: 'admin' }); featuresService = this.owner.lookup('service:features'); }); @@ -247,8 +247,8 @@ module('Acceptance | credential-libraries | create', function (hooks) { details: { request_fields: [ { - name: 'name', - description: 'Name is required.', + name: 'path', + description: 'Vault path is required', }, ], }, @@ -262,7 +262,9 @@ module('Acceptance | credential-libraries | create', function (hooks) { assert .dom(commonSelectors.ALERT_TOAST_BODY) .hasText('The request was invalid'); - assert.dom(commonSelectors.FIELD_NAME_ERROR).hasText('Name is required.'); + assert + .dom(selectors.FIELD_VAULT_PATH_ERROR) + .hasText('Vault path is required'); }); test('cannot select vault ssh cert when feature is disabled', async function (assert) { diff --git a/ui/admin/tests/acceptance/credential-library/delete-test.js b/ui/admin/tests/acceptance/credential-library/delete-test.js index 8312e562ad..03a352c53e 100644 --- a/ui/admin/tests/acceptance/credential-library/delete-test.js +++ b/ui/admin/tests/acceptance/credential-library/delete-test.js @@ -4,23 +4,21 @@ */ import { module, test } from 'qunit'; -import { visit, click, find } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { visit, click } from '@ember/test-helpers'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { authenticateSession } from 'ember-simple-auth/test-support'; import { Response } from 'miragejs'; import { resolve, reject } from 'rsvp'; import sinon from 'sinon'; +import * as selectors from './selectors'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; module('Acceptance | credential-libraries | delete', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); let getCredentialLibraryCount; - const MANAGE_DROPDOWN_SELECTOR = - '[data-test-manage-credential-library-dropdown] button:first-child'; - const DELETE_ACTION_SELECTOR = - '[data-test-manage-credential-library-dropdown] ul li button'; const instances = { scopes: { @@ -31,9 +29,6 @@ module('Acceptance | credential-libraries | delete', function (hooks) { }; const urls = { - globalScope: null, - orgScope: null, - projectScope: null, credentialStores: null, credentialStore: null, credentialLibrary: null, @@ -42,7 +37,7 @@ module('Acceptance | credential-libraries | delete', function (hooks) { unknownCredentialLibrary: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { @@ -61,10 +56,7 @@ module('Acceptance | credential-libraries | delete', function (hooks) { credentialStore: instances.credentialStore, }); // Generate route URLs for resources - urls.globalScope = `/scopes/global/scopes`; - urls.orgScope = `/scopes/${instances.scopes.org.id}/scopes`; - urls.projectScope = `/scopes/${instances.scopes.project.id}`; - urls.credentialStores = `${urls.projectScope}/credential-stores`; + urls.credentialStores = `/scopes/${instances.scopes.project.id}/credential-stores`; urls.credentialStore = `${urls.credentialStores}/${instances.credentialStore.id}`; urls.credentialLibraries = `${urls.credentialStore}/credential-libraries`; urls.credentialLibrary = `${urls.credentialLibraries}/${instances.credentialLibrary.id}`; @@ -73,14 +65,16 @@ module('Acceptance | credential-libraries | delete', function (hooks) { // Generate resource counter getCredentialLibraryCount = () => this.server.schema.credentialLibraries.all().models.length; - authenticateSession({}); + await authenticateSession({}); }); test('can delete resource', async function (assert) { const count = getCredentialLibraryCount(); await visit(urls.credentialLibrary); - await click(MANAGE_DROPDOWN_SELECTOR); - await click(DELETE_ACTION_SELECTOR); + + await click(selectors.MANAGE_DROPDOWN_CRED_LIB); + await click(selectors.MANAGE_DROPDOWN_CRED_LIB_DELETE); + assert.strictEqual(getCredentialLibraryCount(), count - 1); }); @@ -89,10 +83,10 @@ module('Acceptance | credential-libraries | delete', function (hooks) { instances.credentialLibrary.authorized_actions.filter( (item) => item !== 'delete', ); + await visit(urls.credentialLibrary); - assert.notOk( - find('.rose-layout-page-actions .rose-dropdown-button-danger'), - ); + + assert.dom(selectors.MANAGE_DROPDOWN_CRED_LIB).doesNotExist(); }); test('can accept delete credential library via dialog', async function (assert) { @@ -101,8 +95,10 @@ module('Acceptance | credential-libraries | delete', function (hooks) { confirmService.confirm = sinon.fake.returns(resolve()); const count = getCredentialLibraryCount(); await visit(urls.credentialLibrary); - await click(MANAGE_DROPDOWN_SELECTOR); - await click(DELETE_ACTION_SELECTOR); + + await click(selectors.MANAGE_DROPDOWN_CRED_LIB); + await click(selectors.MANAGE_DROPDOWN_CRED_LIB_DELETE); + assert.strictEqual(getCredentialLibraryCount(), count - 1); assert.ok(confirmService.confirm.calledOnce); }); @@ -113,8 +109,10 @@ module('Acceptance | credential-libraries | delete', function (hooks) { confirmService.confirm = sinon.fake.returns(reject()); const count = getCredentialLibraryCount(); await visit(urls.credentialLibrary); - await click(MANAGE_DROPDOWN_SELECTOR); - await click(DELETE_ACTION_SELECTOR); + + await click(selectors.MANAGE_DROPDOWN_CRED_LIB); + await click(selectors.MANAGE_DROPDOWN_CRED_LIB_DELETE); + assert.strictEqual(getCredentialLibraryCount(), count); assert.ok(confirmService.confirm.calledOnce); }); @@ -132,10 +130,10 @@ module('Acceptance | credential-libraries | delete', function (hooks) { ); }); await visit(urls.credentialLibrary); - await click(MANAGE_DROPDOWN_SELECTOR); - await click(DELETE_ACTION_SELECTOR); - assert - .dom('[data-test-toast-notification] .hds-alert__description') - .hasText('Oops.'); + + await click(selectors.MANAGE_DROPDOWN_CRED_LIB); + await click(selectors.MANAGE_DROPDOWN_CRED_LIB_DELETE); + + assert.dom(commonSelectors.ALERT_TOAST_BODY).hasText('Oops.'); }); }); diff --git a/ui/admin/tests/acceptance/credential-library/list-test.js b/ui/admin/tests/acceptance/credential-library/list-test.js index c4bb65b211..c805d00658 100644 --- a/ui/admin/tests/acceptance/credential-library/list-test.js +++ b/ui/admin/tests/acceptance/credential-library/list-test.js @@ -4,15 +4,11 @@ */ import { module, test } from 'qunit'; -import { visit, find } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { visit } from '@ember/test-helpers'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; module('Acceptance | credential-libraries | list', function (hooks) { setupApplicationTest(hooks); @@ -27,16 +23,13 @@ module('Acceptance | credential-libraries | list', function (hooks) { }; const urls = { - globalScope: null, - orgScope: null, - projectScope: null, credentialStores: null, credentialStore: null, credentialLibrary: null, credentialLibraries: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', @@ -54,44 +47,45 @@ module('Acceptance | credential-libraries | list', function (hooks) { credentialStore: instances.credentialStore, }); // Generate route URLs for resources - urls.globalScope = `/scopes/global/scopes`; - urls.orgScope = `/scopes/${instances.scopes.org.id}/scopes`; - urls.projectScope = `/scopes/${instances.scopes.project.id}`; - urls.credentialStores = `${urls.projectScope}/credential-stores`; + urls.credentialStores = `/scopes/${instances.scopes.project.id}/credential-stores`; urls.credentialStore = `${urls.credentialStores}/${instances.credentialStore.id}`; urls.credentialLibraries = `${urls.credentialStore}/credential-libraries`; - urls.credentialLibrary = `${urls.credentialLibraries}/${instances.credentialLibrary.id}`; - authenticateSession({}); + await authenticateSession({}); }); test('Users can navigate to credential libraries with proper authorization', async function (assert) { await visit(urls.credentialStore); + assert.ok( instances.credentialStore.authorized_collection_actions[ 'credential-libraries' ].includes('list'), ); - assert.ok(find(`[href="${urls.credentialLibraries}"]`)); + assert.dom(commonSelectors.HREF(urls.credentialLibraries)).isVisible(); }); test('Users cannot navigate to index without either list or create actions', async function (assert) { instances.credentialStore.authorized_collection_actions[ 'credential-libraries' ] = []; + await visit(urls.credentialStore); + assert.notOk( instances.credentialStore.authorized_collection_actions[ 'credential-libraries' ].includes('list'), ); - assert.notOk(find(`[href="${urls.credentialLibraries}"]`)); + assert.dom(commonSelectors.HREF(urls.credentialLibraries)).doesNotExist(); }); test('Users can navigate to index with only create action', async function (assert) { instances.credentialStore.authorized_collection_actions[ 'credential-libraries' ] = ['create']; + await visit(urls.credentialStore); - assert.ok(find(`[href="${urls.credentialLibraries}"]`)); + + assert.dom(commonSelectors.HREF(urls.credentialLibraries)).isVisible(); }); }); diff --git a/ui/admin/tests/acceptance/credential-library/read-test.js b/ui/admin/tests/acceptance/credential-library/read-test.js index 4bf3cacdb9..87fd411901 100644 --- a/ui/admin/tests/acceptance/credential-library/read-test.js +++ b/ui/admin/tests/acceptance/credential-library/read-test.js @@ -4,12 +4,13 @@ */ import { module, test } from 'qunit'; -import { visit, click, currentURL, find } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { visit, click, currentURL } from '@ember/test-helpers'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { authenticateSession } from 'ember-simple-auth/test-support'; import { TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE } from 'api/models/credential-library'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; module('Acceptance | credential-libraries | read', function (hooks) { setupApplicationTest(hooks); @@ -33,9 +34,10 @@ module('Acceptance | credential-libraries | read', function (hooks) { credentialLibraries: null, newCredentialLibrary: null, unknownCredentialLibrary: null, + vaultSshCredentialLibrary: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { @@ -63,13 +65,15 @@ module('Acceptance | credential-libraries | read', function (hooks) { urls.credentialLibrary = `${urls.credentialLibraries}/${instances.credentialLibrary.id}`; urls.newCredentialLibrary = `${urls.credentialLibraries}/new`; urls.unknownCredentialLibrary = `${urls.credentialLibraries}/foo`; - authenticateSession({ username: 'admin' }); + await authenticateSession({ username: 'admin' }); }); test('can navigate to resource', async function (assert) { await visit(urls.credentialLibraries); - await click('.hds-table .hds-table__tbody .hds-table__tr a'); + + await click(commonSelectors.TABLE_RESOURCE_LINK(urls.credentialLibrary)); await a11yAudit(); + assert.strictEqual(currentURL(), urls.credentialLibrary); }); @@ -79,7 +83,10 @@ module('Acceptance | credential-libraries | read', function (hooks) { (item) => item !== 'read', ); await visit(urls.credentialLibraries); - assert.notOk(find('main tbody .rose-table-header-cell a')); + + assert + .dom(commonSelectors.TABLE_RESOURCE_LINK(urls.credentialLibrary)) + .doesNotExist(); }); test('cannot navigate to vault ssh cert form when feature is not enabled', async function (assert) { @@ -88,22 +95,28 @@ module('Acceptance | credential-libraries | read', function (hooks) { credentialStore: instances.credentialStore, type: TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE, }); - await visit( - `${urls.credentialLibraries}/${instances.credentialLibrary.id}`, - ); + urls.vaultSshCredentialLibrary = `${urls.credentialLibraries}/${instances.credentialLibrary.id}`; await visit(urls.credentialLibraries); const featuresService = this.owner.lookup('service:features'); + assert.false(featuresService.isEnabled('ssh-target')); - assert.dom('.rose-table-body tr:nth-of-type(2) a').doesNotExist(); + assert + .dom(commonSelectors.TABLE_RESOURCE_LINK(urls.credentialLibrary)) + .isVisible(); + assert + .dom(commonSelectors.TABLE_RESOURCE_LINK(urls.vaultSshCredentialLibrary)) + .doesNotExist(); }); test('visiting an unknown credential library displays 404 message', async function (assert) { await visit(urls.unknownCredentialLibrary); await a11yAudit(); - assert.ok(find('.rose-message-subtitle').textContent.trim(), 'Error 404'); + assert + .dom(commonSelectors.RESOURCE_NOT_FOUND_SUBTITLE) + .hasText(commonSelectors.RESOURCE_NOT_FOUND_VALUE); }); - test('users can navigate to credential library and incorrect url autocorrects', async function (assert) { + test('users can navigate to credential library and incorrect url auto-corrects', async function (assert) { const credentialStore = this.server.create('credential-store', { scope: instances.scopes.project, }); diff --git a/ui/admin/tests/acceptance/credential-library/selectors.js b/ui/admin/tests/acceptance/credential-library/selectors.js index 1fe15f723d..1cc6a76371 100644 --- a/ui/admin/tests/acceptance/credential-library/selectors.js +++ b/ui/admin/tests/acceptance/credential-library/selectors.js @@ -8,6 +8,7 @@ export const FIELD_CRED_TYPE_VALUE = 'ssh_private_key'; export const FIELD_VAULT_PATH = '[name=vault_path]'; export const FIELD_VAULT_PATH_VALUE = 'path'; +export const FIELD_VAULT_PATH_ERROR = '[data-test-error-message-vault-path]'; export const FIELD_USERNAME = '[name=username]'; export const FIELD_USERNAME_VALUE = 'username'; @@ -45,3 +46,8 @@ export const FIELD_EXT_KEY = export const FIELD_EXT_VALUE = '[name="extensions"] tbody td:nth-of-type(2) input'; export const FIELD_EXT_BTN = '[name="extensions"] button'; + +export const MANAGE_DROPDOWN_CRED_LIB = + '[data-test-manage-credential-library-dropdown] button:first-child'; +export const MANAGE_DROPDOWN_CRED_LIB_DELETE = + '[data-test-manage-credential-library-dropdown] ul li button'; diff --git a/ui/admin/tests/acceptance/credential-library/update-test.js b/ui/admin/tests/acceptance/credential-library/update-test.js index 29d8111875..6aa318daeb 100644 --- a/ui/admin/tests/acceptance/credential-library/update-test.js +++ b/ui/admin/tests/acceptance/credential-library/update-test.js @@ -4,35 +4,19 @@ */ import { module, test } from 'qunit'; -import { - visit, - click, - fillIn, - currentURL, - find, - select, -} from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { visit, click, fillIn, currentURL, select } from '@ember/test-helpers'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { authenticateSession } from 'ember-simple-auth/test-support'; import { Response } from 'miragejs'; import { TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE } from 'api/models/credential-library'; +import * as selectors from './selectors'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; module('Acceptance | credential-libraries | update', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - const BUTTON_SELECTOR = '.rose-form-actions [type="button"]'; - const SAVE_BTN_SELECTOR = '.rose-form-actions [type="submit"]'; - const NAME_INPUT_SELECTOR = '[name="name"]'; - const FIELD_ERROR_TEXT_SELECTOR = '.hds-form-error__message'; - const DESC_INPUT_SELECTOR = '[name="description"]'; - const VAULT_PATH_SELECTOR = '[name="vault_path"]'; - const CRED_TYPE_SELECTOR = '[name="credential_type"]'; - const CRED_MAPPING_OVERRIDES_SELECT = - '[name="credential_mapping_overrides"] tbody td:nth-of-type(1) select'; - const CRED_MAPPING_OVERRIDES_INPUT = - '[name="credential_mapping_overrides"] tbody td:nth-of-type(2) input'; const instances = { scopes: { global: null, @@ -53,7 +37,7 @@ module('Acceptance | credential-libraries | update', function (hooks) { unknownCredentialLibrary: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { @@ -81,7 +65,7 @@ module('Acceptance | credential-libraries | update', function (hooks) { urls.credentialLibrary = `${urls.credentialLibraries}/${instances.credentialLibrary.id}`; urls.newCredentialLibrary = `${urls.credentialLibraries}/new`; urls.unknownCredentialLibrary = `${urls.credentialLibraries}/foo`; - authenticateSession({}); + await authenticateSession({}); }); test('cannot update resource without proper authorization', async function (assert) { @@ -89,48 +73,60 @@ module('Acceptance | credential-libraries | update', function (hooks) { instances.credentialLibrary.authorized_actions.filter( (item) => item !== 'update', ); + await visit(urls.credentialLibrary); - assert.notOk(find(BUTTON_SELECTOR)); + + assert.dom(commonSelectors.EDIT_BTN).doesNotExist(); }); test('can update a credential library and cancel changes', async function (assert) { await visit(urls.credentialLibrary); - await click(BUTTON_SELECTOR, 'Activate edit mode'); - await fillIn(NAME_INPUT_SELECTOR, 'random string'); - await click(BUTTON_SELECTOR); + await click(commonSelectors.EDIT_BTN, 'Activate edit mode'); + await fillIn(commonSelectors.FIELD_NAME, commonSelectors.FIELD_NAME_VALUE); + await click(commonSelectors.CANCEL_BTN); + assert.notEqual( this.server.schema.credentialLibraries.all().models[0].name, - 'random string', - ); - assert.strictEqual( - find(NAME_INPUT_SELECTOR).value, - instances.credentialLibrary.name, + commonSelectors.FIELD_NAME_VALUE, ); + assert + .dom(commonSelectors.FIELD_NAME) + .hasValue(instances.credentialLibrary.name); }); test('can update a vault generic credential library and save changes', async function (assert) { - await visit( - `${urls.credentialLibraries}/${instances.credentialLibrary.id}`, - ); - - await click(BUTTON_SELECTOR); - - await fillIn(NAME_INPUT_SELECTOR, 'random string'); - await fillIn(DESC_INPUT_SELECTOR, 'description'); - await fillIn(VAULT_PATH_SELECTOR, 'path'); - - await select(CRED_MAPPING_OVERRIDES_SELECT, 'private_key_attribute'); - await fillIn(CRED_MAPPING_OVERRIDES_INPUT, 'key'); + await visit(urls.credentialLibrary); - await click('[name="credential_mapping_overrides"] button'); + await click(commonSelectors.EDIT_BTN); + await fillIn(commonSelectors.FIELD_NAME, commonSelectors.FIELD_NAME_VALUE); + await fillIn( + commonSelectors.FIELD_DESCRIPTION, + commonSelectors.FIELD_DESCRIPTION_VALUE, + ); + await fillIn(selectors.FIELD_VAULT_PATH, selectors.FIELD_VAULT_PATH_VALUE); + await select( + selectors.FIELD_CRED_MAP_OVERRIDES_SELECT, + selectors.FIELD_CRED_MAP_OVERRIDES_SELECT_VALUE, + ); + await fillIn(selectors.FIELD_CRED_MAP_OVERRIDES_INPUT, 'key'); + await click(selectors.FIELD_CRED_MAP_OVERRIDES_BTN); + await click(commonSelectors.SAVE_BTN); - await click(SAVE_BTN_SELECTOR); const credentialLibrary = this.server.schema.credentialLibraries.findBy({ - name: 'random string', + name: commonSelectors.FIELD_NAME_VALUE, }); - assert.strictEqual(credentialLibrary.name, 'random string'); - assert.strictEqual(credentialLibrary.description, 'description'); - assert.strictEqual(credentialLibrary.attributes.path, 'path'); + assert.strictEqual( + credentialLibrary.name, + commonSelectors.FIELD_NAME_VALUE, + ); + assert.strictEqual( + credentialLibrary.description, + commonSelectors.FIELD_DESCRIPTION_VALUE, + ); + assert.strictEqual( + credentialLibrary.attributes.path, + selectors.FIELD_VAULT_PATH_VALUE, + ); assert.deepEqual(credentialLibrary.credentialMappingOverrides, { private_key_attribute: 'key', }); @@ -148,8 +144,8 @@ module('Acceptance | credential-libraries | update', function (hooks) { details: { request_fields: [ { - name: 'name', - description: 'Name is required', + name: 'path', + description: 'Vault path is required', }, ], }, @@ -157,38 +153,45 @@ module('Acceptance | credential-libraries | update', function (hooks) { ); }); await visit(urls.credentialLibrary); - await click(BUTTON_SELECTOR, 'Activate edit mode'); - await fillIn(NAME_INPUT_SELECTOR, 'random string'); - await click(SAVE_BTN_SELECTOR); + await click(commonSelectors.EDIT_BTN, 'Activate edit mode'); + await fillIn(commonSelectors.FIELD_NAME, commonSelectors.FIELD_NAME_VALUE); + await click(commonSelectors.SAVE_BTN); + assert - .dom('[data-test-toast-notification] .hds-alert__description') + .dom(commonSelectors.ALERT_TOAST_BODY) .hasText('The request was invalid.'); - assert.ok( - find(FIELD_ERROR_TEXT_SELECTOR).textContent.trim(), - 'Name is required.', - ); + assert + .dom(selectors.FIELD_VAULT_PATH_ERROR) + .hasText('Vault path is required'); }); test('can discard unsaved credential library changes via dialog', async function (assert) { assert.expect(5); const confirmService = this.owner.lookup('service:confirm'); confirmService.enabled = true; - assert.notEqual(instances.credentialLibrary.name, 'random string'); + + assert.notEqual( + instances.credentialLibrary.name, + commonSelectors.FIELD_NAME_VALUE, + ); await visit(urls.credentialLibrary); - await click(BUTTON_SELECTOR, 'Activate edit mode'); - await fillIn(NAME_INPUT_SELECTOR, 'random string'); + await click(commonSelectors.EDIT_BTN, 'Activate edit mode'); + await fillIn(commonSelectors.FIELD_NAME, commonSelectors.FIELD_NAME_VALUE); + assert.strictEqual(currentURL(), urls.credentialLibrary); try { await visit(urls.credentialLibraries); } catch (e) { - assert.ok(find('.rose-dialog')); - await click('.rose-dialog-footer button:first-child', 'Click Discard'); + assert.dom(commonSelectors.MODAL_WARNING).isVisible(); + + await click(commonSelectors.MODAL_WARNING_CONFIRM_BTN); + assert.strictEqual(currentURL(), urls.credentialLibraries); assert.notEqual( this.server.schema.credentialLibraries.all().models[0].name, - 'random string', + commonSelectors.FIELD_NAME_VALUE, ); } }); @@ -197,29 +200,38 @@ module('Acceptance | credential-libraries | update', function (hooks) { assert.expect(5); const confirmService = this.owner.lookup('service:confirm'); confirmService.enabled = true; - assert.notEqual(instances.credentialLibrary.name, 'random string'); + + assert.notEqual( + instances.credentialLibrary.name, + commonSelectors.FIELD_NAME_VALUE, + ); + await visit(urls.credentialLibrary); - await click(BUTTON_SELECTOR, 'Activate edit mode'); - await fillIn(NAME_INPUT_SELECTOR, 'random string'); + await click(commonSelectors.EDIT_BTN, 'Activate edit mode'); + await fillIn(commonSelectors.FIELD_NAME, commonSelectors.FIELD_NAME_VALUE); + assert.strictEqual(currentURL(), urls.credentialLibrary); try { await visit(urls.credentialLibraries); - } catch (e) { - assert.ok(find('.rose-dialog')); - await click('.rose-dialog-footer button:last-child'); + } catch { + assert.dom(commonSelectors.MODAL_WARNING).isVisible(); + + await click(commonSelectors.MODAL_WARNING_CANCEL_BTN); + assert.strictEqual(currentURL(), urls.credentialLibrary); assert.notEqual( this.server.schema.credentialLibraries.all().models[0].name, - 'random string', + commonSelectors.FIELD_NAME_VALUE, ); } }); test('cannot update credential type in a vault generic credential library', async function (assert) { await visit(urls.credentialLibrary); - await click(BUTTON_SELECTOR, 'Activate edit mode'); - assert.dom(CRED_TYPE_SELECTOR).isDisabled(); + await click(commonSelectors.EDIT_BTN, 'Activate edit mode'); + + assert.dom(selectors.FIELD_CRED_TYPE).isDisabled(); }); test('can update a vault ssh cert credential library and save changes', async function (assert) { @@ -231,47 +243,61 @@ module('Acceptance | credential-libraries | update', function (hooks) { await visit( `${urls.credentialLibraries}/${instances.credentialLibrary.id}`, ); - await click(BUTTON_SELECTOR, 'Activate edit mode'); - await fillIn(NAME_INPUT_SELECTOR, 'name'); - await fillIn(DESC_INPUT_SELECTOR, 'description'); - await fillIn(VAULT_PATH_SELECTOR, 'path'); - await fillIn('[name="username"]', 'username'); - await select('[name="key_type"]', 'rsa'); - await fillIn('[name="key_bits"]', 100); - await fillIn('[name="ttl"]', 'ttl'); - await fillIn('[name="key_id"]', 'key_id'); - await fillIn( - '[name="critical_options"] tbody td:nth-of-type(1) input', - 'co_key', - ); - await fillIn( - '[name="critical_options"] tbody td:nth-of-type(2) input', - 'co_value', - ); - await click('[name="critical_options"] button'); - await fillIn( - '[name="extensions"] tbody td:nth-of-type(1) input', - 'ext_key', - ); + await click(commonSelectors.EDIT_BTN, 'Activate edit mode'); + await fillIn(commonSelectors.FIELD_NAME, commonSelectors.FIELD_NAME_VALUE); await fillIn( - '[name="extensions"] tbody td:nth-of-type(2) input', - 'ext_value', + commonSelectors.FIELD_DESCRIPTION, + commonSelectors.FIELD_DESCRIPTION_VALUE, ); + await fillIn(selectors.FIELD_VAULT_PATH, selectors.FIELD_VAULT_PATH_VALUE); + await fillIn(selectors.FIELD_USERNAME, selectors.FIELD_USERNAME_VALUE); + await fillIn(selectors.FIELD_KEY_TYPE, selectors.FIELD_KEY_TYPE_VALUE); + await fillIn(selectors.FIELD_KEY_BITS, selectors.FIELD_KEY_BITS_VALUE); + await fillIn(selectors.FIELD_TTL, selectors.FIELD_TTL_VALUE); + await fillIn(selectors.FIELD_KEY_ID, selectors.FIELD_KEY_ID_VALUE); + await fillIn(selectors.FIELD_CRIT_OPTS_KEY, 'co_key'); + await fillIn(selectors.FIELD_CRIT_OPTS_VALUE, 'co_value'); + await click(selectors.FIELD_CRIT_OPTS_BTN); + await fillIn(selectors.FIELD_EXT_KEY, 'ext_key'); + await fillIn(selectors.FIELD_EXT_VALUE, 'ext_value'); + await click(selectors.FIELD_EXT_BTN); + await click(commonSelectors.SAVE_BTN); - await click('[name="extensions"] button'); - - await click(SAVE_BTN_SELECTOR); const credentialLibrary = this.server.schema.credentialLibraries.findBy({ type: TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE, }); - assert.strictEqual(credentialLibrary.name, 'name'); - assert.strictEqual(credentialLibrary.description, 'description'); - assert.strictEqual(credentialLibrary.attributes.path, 'path'); - assert.strictEqual(credentialLibrary.attributes.username, 'username'); - assert.strictEqual(credentialLibrary.attributes.key_type, 'rsa'); - assert.strictEqual(credentialLibrary.attributes.key_bits, 100); - assert.strictEqual(credentialLibrary.attributes.ttl, 'ttl'); - assert.strictEqual(credentialLibrary.attributes.key_id, 'key_id'); + assert.strictEqual( + credentialLibrary.name, + commonSelectors.FIELD_NAME_VALUE, + ); + assert.strictEqual( + credentialLibrary.description, + commonSelectors.FIELD_DESCRIPTION_VALUE, + ); + assert.strictEqual( + credentialLibrary.attributes.path, + selectors.FIELD_VAULT_PATH_VALUE, + ); + assert.strictEqual( + credentialLibrary.attributes.username, + selectors.FIELD_USERNAME_VALUE, + ); + assert.strictEqual( + credentialLibrary.attributes.key_type, + selectors.FIELD_KEY_TYPE_VALUE, + ); + assert.strictEqual( + credentialLibrary.attributes.key_bits, + selectors.FIELD_KEY_BITS_VALUE, + ); + assert.strictEqual( + credentialLibrary.attributes.ttl, + selectors.FIELD_TTL_VALUE, + ); + assert.strictEqual( + credentialLibrary.attributes.key_id, + selectors.FIELD_KEY_ID_VALUE, + ); assert.deepEqual(credentialLibrary.attributes.critical_options, { co_key: 'co_value', }); diff --git a/ui/admin/tests/acceptance/credential-store/create-test.js b/ui/admin/tests/acceptance/credential-store/create-test.js index 5c0d447a20..24f09d4a47 100644 --- a/ui/admin/tests/acceptance/credential-store/create-test.js +++ b/ui/admin/tests/acceptance/credential-store/create-test.js @@ -4,12 +4,14 @@ */ import { module, test } from 'qunit'; -import { visit, currentURL, find, click, fillIn } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { visit, currentURL, click, fillIn } from '@ember/test-helpers'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import { authenticateSession } from 'ember-simple-auth/test-support'; import { Response } from 'miragejs'; +import * as selectors from './selectors'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; module('Acceptance | credential-stores | create', function (hooks) { setupApplicationTest(hooks); @@ -30,13 +32,11 @@ module('Acceptance | credential-stores | create', function (hooks) { }; const urls = { - globalScope: null, - projectScope: null, credentialStores: null, newCredentialStore: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { @@ -48,9 +48,7 @@ module('Acceptance | credential-stores | create', function (hooks) { scope: { id: instances.scopes.org.id, type: 'org' }, }); // Generate route URLs for resources - urls.globalScope = `/scopes/global/scopes`; - urls.projectScope = `/scopes/${instances.scopes.project.id}`; - urls.credentialStores = `${urls.projectScope}/credential-stores`; + urls.credentialStores = `/scopes/${instances.scopes.project.id}/credential-stores`; urls.newCredentialStore = `${urls.credentialStores}/new`; // Generate resource counter getCredentialStoresCount = () => { @@ -64,7 +62,7 @@ module('Acceptance | credential-stores | create', function (hooks) { return this.server.schema.credentialStores.where({ type: 'vault' }).models .length; }; - authenticateSession({}); + await authenticateSession({}); featuresService = this.owner.lookup('service:features'); }); @@ -72,8 +70,10 @@ module('Acceptance | credential-stores | create', function (hooks) { featuresService.enable('static-credentials'); const count = getStaticCredentialStoresCount(); await visit(urls.newCredentialStore); - await fillIn('[name="name"]', 'random string'); - await click('[type="submit"]'); + + await fillIn(commonSelectors.FIELD_NAME, commonSelectors.FIELD_NAME_VALUE); + await click(commonSelectors.SAVE_BTN); + assert.strictEqual(getStaticCredentialStoresCount(), count + 1); }); @@ -81,17 +81,21 @@ module('Acceptance | credential-stores | create', function (hooks) { featuresService.enable('static-credentials'); const count = getVaultCredentialStoresCount(); await visit(urls.newCredentialStore); - await fillIn('[name="name"]', 'random string'); - await click('[value="vault"]'); - await click('[type="submit"]'); + + await fillIn(commonSelectors.FIELD_NAME, commonSelectors.FIELD_NAME_VALUE); + await click(selectors.TYPE_VAULT); + await click(commonSelectors.SAVE_BTN); + assert.strictEqual(getVaultCredentialStoresCount(), count + 1); }); test('Users can cancel create new credential stores', async function (assert) { const count = getCredentialStoresCount(); await visit(urls.newCredentialStore); - await fillIn('[name="name"]', 'random string'); - await click('.rose-form-actions [type="button"]'); + + await fillIn(commonSelectors.FIELD_NAME, commonSelectors.FIELD_NAME_VALUE); + await click(commonSelectors.CANCEL_BTN); + assert.strictEqual(currentURL(), urls.credentialStores); assert.strictEqual(getCredentialStoresCount(), count); }); @@ -101,15 +105,18 @@ module('Acceptance | credential-stores | create', function (hooks) { 'credential-stores' ] = []; await visit(urls.credentialStores); + assert.notOk( instances.scopes.project.authorized_collection_actions[ 'credential-stores' ].includes('create'), ); - assert.notOk(find(`[href="${urls.newCredentialStore}"]`)); + assert.dom(commonSelectors.HREF(urls.newCredentialStore)).doesNotExist(); }); - test('saving a new credential store with invalid fields displays error messages', async function (assert) { + test('saving a new static credential store with invalid fields displays error messages', async function (assert) { + const errorMsg = + 'Invalid request. Request attempted to make second resource with the same field value that must be unique.'; this.server.post('/credential-stores', () => { return new Response( 400, @@ -117,7 +124,7 @@ module('Acceptance | credential-stores | create', function (hooks) { { status: 400, code: 'invalid_argument', - message: 'The request was invalid.', + message: errorMsg, details: { request_fields: [ { @@ -130,23 +137,55 @@ module('Acceptance | credential-stores | create', function (hooks) { ); }); await visit(urls.newCredentialStore); - await click('[type="submit"]'); + + await click(commonSelectors.SAVE_BTN); + + assert.dom(commonSelectors.ALERT_TOAST_BODY).hasText(errorMsg); + }); + + test('saving a new vault credential store with invalid fields displays error messages', async function (assert) { + this.server.post('/credential-stores', () => { + return new Response( + 400, + {}, + { + status: 400, + code: 'invalid_argument', + message: 'The request was invalid.', + details: { + request_fields: [ + { + name: 'attributes.address', + description: + 'Field required for creating a vault credential store.', + }, + ], + }, + }, + ); + }); + await visit(urls.newCredentialStore); + + await click(commonSelectors.SAVE_BTN); + assert - .dom('[data-test-toast-notification] .hds-alert__description') + .dom(commonSelectors.ALERT_TOAST_BODY) .hasText('The request was invalid.'); - assert.ok( - find('[data-test-error-message-name]').textContent.trim(), - 'Name is required.', - ); + assert + .dom(selectors.FIELD_VAULT_ADDRESS_ERROR) + .hasText('Field required for creating a vault credential store.'); }); test('Users can link to docs page for new credential store', async function (assert) { await visit(urls.newCredentialStore); - assert.ok( - find( - `[href="https://developer.hashicorp.com/boundary/docs/concepts/domain-model/credential-stores"]`, - ), - ); + + assert + .dom( + commonSelectors.HREF( + 'https://developer.hashicorp.com/boundary/docs/concepts/domain-model/credential-stores', + ), + ) + .isVisible(); }); test('users cannot directly navigate to new credential store route without proper authorization', async function (assert) { diff --git a/ui/admin/tests/acceptance/credential-store/credentials/create-test.js b/ui/admin/tests/acceptance/credential-store/credentials/create-test.js index a9955f5eaa..2dbfa320fe 100644 --- a/ui/admin/tests/acceptance/credential-store/credentials/create-test.js +++ b/ui/admin/tests/acceptance/credential-store/credentials/create-test.js @@ -5,7 +5,7 @@ import { module, test } from 'qunit'; import { visit, currentURL, click, fillIn } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import { authenticateSession } from 'ember-simple-auth/test-support'; @@ -40,7 +40,7 @@ module( newCredential: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Generate resources featuresService = this.owner.lookup('service:features'); instances.scopes.org = this.server.create('scope', { @@ -77,7 +77,7 @@ module( getJsonCredentialCount = () => { return this.server.schema.credentials.where({ type: 'json' }).length; }; - authenticateSession({}); + await authenticateSession({}); }); test('users can create a new username & password credential', async function (assert) { diff --git a/ui/admin/tests/acceptance/credential-store/credentials/delete-test.js b/ui/admin/tests/acceptance/credential-store/credentials/delete-test.js index 3fc5683f9d..9ebb266ec1 100644 --- a/ui/admin/tests/acceptance/credential-store/credentials/delete-test.js +++ b/ui/admin/tests/acceptance/credential-store/credentials/delete-test.js @@ -4,11 +4,12 @@ */ import { module, test } from 'qunit'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { authenticateSession } from 'ember-simple-auth/test-support'; import { click, currentURL, visit } from '@ember/test-helpers'; import { Response } from 'miragejs'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; module( 'Acceptance | credential-stores | credentials | delete', @@ -42,7 +43,7 @@ module( jsonCredential: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Generate resources instances.scopes.org = this.server.create('scope', { type: 'org', @@ -92,7 +93,7 @@ module( getJSONCredentialCount = () => { return this.server.schema.credentials.where({ type: 'json' }).length; }; - authenticateSession({}); + await authenticateSession({}); }); test('can delete username & password credential', async function (assert) { @@ -182,7 +183,7 @@ module( await visit(urls.usernamePasswordCredential); await click(MANAGE_DROPDOWN_SELECTOR); await click(DELETE_ACTION_SELECTOR); - await click('.rose-dialog footer .rose-button-primary'); + await click(commonSelectors.MODAL_WARNING_CONFIRM_BTN); assert.strictEqual(currentURL(), urls.credentials); assert.strictEqual( getUsernamePasswordCredentialCount(), @@ -198,7 +199,7 @@ module( await visit(urls.usernameKeyPairCredential); await click(MANAGE_DROPDOWN_SELECTOR); await click(DELETE_ACTION_SELECTOR); - await click('.rose-dialog footer .rose-button-primary'); + await click(commonSelectors.MODAL_WARNING_CONFIRM_BTN); assert.strictEqual(currentURL(), urls.credentials); assert.strictEqual( getUsernameKeyPairCredentialCount(), @@ -213,7 +214,7 @@ module( await visit(urls.jsonCredential); await click(MANAGE_DROPDOWN_SELECTOR); await click(DELETE_ACTION_SELECTOR); - await click('.rose-dialog footer .rose-button-primary'); + await click(commonSelectors.MODAL_WARNING_CONFIRM_BTN); assert.strictEqual(currentURL(), urls.credentials); assert.strictEqual(getJSONCredentialCount(), jsonCredentialCount - 1); }); @@ -226,7 +227,7 @@ module( await visit(urls.usernamePasswordCredential); await click(MANAGE_DROPDOWN_SELECTOR); await click(DELETE_ACTION_SELECTOR); - await click('.rose-dialog footer .rose-button-secondary'); + await click(commonSelectors.MODAL_WARNING_CANCEL_BTN); assert.strictEqual(currentURL(), urls.usernamePasswordCredential); assert.strictEqual( getUsernamePasswordCredentialCount(), @@ -242,7 +243,7 @@ module( await visit(urls.usernameKeyPairCredential); await click(MANAGE_DROPDOWN_SELECTOR); await click(DELETE_ACTION_SELECTOR); - await click('.rose-dialog footer .rose-button-secondary'); + await click(commonSelectors.MODAL_WARNING_CANCEL_BTN); assert.strictEqual(currentURL(), urls.usernameKeyPairCredential); assert.strictEqual( getUsernameKeyPairCredentialCount(), @@ -257,7 +258,7 @@ module( await visit(urls.jsonCredential); await click(MANAGE_DROPDOWN_SELECTOR); await click(DELETE_ACTION_SELECTOR); - await click('.rose-dialog footer .rose-button-secondary'); + await click(commonSelectors.MODAL_WARNING_CANCEL_BTN); assert.strictEqual(currentURL(), urls.jsonCredential); assert.strictEqual(getJSONCredentialCount(), jsonCredentialCount); }); diff --git a/ui/admin/tests/acceptance/credential-store/credentials/list-test.js b/ui/admin/tests/acceptance/credential-store/credentials/list-test.js index 072b24d9fe..7154d4eb14 100644 --- a/ui/admin/tests/acceptance/credential-store/credentials/list-test.js +++ b/ui/admin/tests/acceptance/credential-store/credentials/list-test.js @@ -5,7 +5,7 @@ import { module, test } from 'qunit'; import { visit, click, currentURL } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { authenticateSession } from 'ember-simple-auth/test-support'; @@ -38,7 +38,7 @@ module('Acceptance | credential-stores | credentials | list', function (hooks) { newCredential: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Generate resources instances.scopes.org = this.server.create('scope', { type: 'org', @@ -73,7 +73,7 @@ module('Acceptance | credential-stores | credentials | list', function (hooks) { urls.staticCredentialStore = `${urls.credentialStores}/${instances.staticCredentialStore.id}`; urls.credentials = `${urls.staticCredentialStore}/credentials`; urls.newCredential = `${urls.staticCredentialStore}/credentials/new`; - authenticateSession({}); + await authenticateSession({}); }); test('Users can navigate to credentials with proper authorization', async function (assert) { diff --git a/ui/admin/tests/acceptance/credential-store/credentials/read-test.js b/ui/admin/tests/acceptance/credential-store/credentials/read-test.js index 6c1e538b33..4af24cd7df 100644 --- a/ui/admin/tests/acceptance/credential-store/credentials/read-test.js +++ b/ui/admin/tests/acceptance/credential-store/credentials/read-test.js @@ -5,10 +5,11 @@ import { module, test } from 'qunit'; import { visit, currentURL, find, click } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { authenticateSession } from 'ember-simple-auth/test-support'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; module('Acceptance | credential-stores | credentials | read', function (hooks) { setupApplicationTest(hooks); @@ -38,7 +39,7 @@ module('Acceptance | credential-stores | credentials | read', function (hooks) { unknownCredential: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Generate resources instances.scopes.org = this.server.create('scope', { type: 'org', @@ -76,7 +77,7 @@ module('Acceptance | credential-stores | credentials | read', function (hooks) { urls.usernameKeyPairCredential = `${urls.credentials}/${instances.usernameKeyPairCredential.id}`; urls.jsonCredential = `${urls.credentials}/${instances.jsonCredential.id}`; urls.unknownCredential = `${urls.credentials}/foo`; - authenticateSession({ username: 'admin' }); + await authenticateSession({ username: 'admin' }); featuresService = this.owner.lookup('service:features'); }); @@ -117,7 +118,13 @@ module('Acceptance | credential-stores | credentials | read', function (hooks) { (item) => item != 'read', ); await visit(urls.credentials); - assert.dom('.rose-table-body tr:first-child a').doesNotExist(); + + assert + .dom(commonSelectors.TABLE_RESOURCE_LINK(urls.usernameKeyPairCredential)) + .isVisible(); + assert + .dom(commonSelectors.TABLE_RESOURCE_LINK(urls.usernamePasswordCredential)) + .doesNotExist(); }); test('cannot navigate to a username & key pair credential form without proper authorization', async function (assert) { @@ -126,7 +133,13 @@ module('Acceptance | credential-stores | credentials | read', function (hooks) { (item) => item != 'read', ); await visit(urls.credentials); - assert.dom('.rose-table-body tr:nth-child(2) a').doesNotExist(); + + assert + .dom(commonSelectors.TABLE_RESOURCE_LINK(urls.usernamePasswordCredential)) + .isVisible(); + assert + .dom(commonSelectors.TABLE_RESOURCE_LINK(urls.usernameKeyPairCredential)) + .doesNotExist(); }); test('cannot navigate to a JSON credential form without proper authorization', async function (assert) { @@ -135,19 +148,33 @@ module('Acceptance | credential-stores | credentials | read', function (hooks) { (item) => item != 'read', ); await visit(urls.credentials); - assert.dom('.rose-table-body tr:nth-child(3) a').doesNotExist(); + + assert + .dom(commonSelectors.TABLE_RESOURCE_LINK(urls.usernamePasswordCredential)) + .isVisible(); + assert + .dom(commonSelectors.TABLE_RESOURCE_LINK(urls.jsonCredential)) + .doesNotExist(); }); test('cannot navigate to a JSON credential form when feature not enabled', async function (assert) { await visit(urls.credentials); + assert.false(featuresService.isEnabled('json-credentials')); - assert.dom('.rose-table-body tr:nth-child(3) a').doesNotExist(); + assert + .dom(commonSelectors.TABLE_RESOURCE_LINK(urls.usernamePasswordCredential)) + .isVisible(); + assert + .dom(commonSelectors.TABLE_RESOURCE_LINK(urls.jsonCredential)) + .doesNotExist(); }); test('visiting an unknown credential displays 404 message', async function (assert) { await visit(urls.unknownCredential); await a11yAudit(); - assert.ok(find('.rose-message-subtitle').textContent.trim(), 'Error 404'); + assert + .dom(commonSelectors.RESOURCE_NOT_FOUND_SUBTITLE) + .hasText(commonSelectors.RESOURCE_NOT_FOUND_VALUE); }); test('Users can link to docs page for credential', async function (assert) { @@ -159,7 +186,7 @@ module('Acceptance | credential-stores | credentials | read', function (hooks) { ); }); - test('users can navigate to credential and incorrect url autocorrects', async function (assert) { + test('users can navigate to credential and incorrect url auto-corrects', async function (assert) { const credentialStore = this.server.create('credential-store', { scope: instances.scopes.project, }); diff --git a/ui/admin/tests/acceptance/credential-store/credentials/update-test.js b/ui/admin/tests/acceptance/credential-store/credentials/update-test.js index d0d0e38150..e918e71374 100644 --- a/ui/admin/tests/acceptance/credential-store/credentials/update-test.js +++ b/ui/admin/tests/acceptance/credential-store/credentials/update-test.js @@ -12,10 +12,11 @@ import { fillIn, waitUntil, } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { Response } from 'miragejs'; import { authenticateSession } from 'ember-simple-auth/test-support'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; module( 'Acceptance | credential-stores | credentials | update', @@ -47,7 +48,7 @@ module( jsonCredential: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { @@ -87,7 +88,7 @@ module( urls.jsonCredential = `${urls.credentials}/${instances.jsonCredential.id}`; featuresService = this.owner.lookup('service:features'); - authenticateSession({}); + await authenticateSession({}); }); test('can save changes to existing username & password credential', async function (assert) { @@ -322,11 +323,8 @@ module( try { await visit(urls.credentials); } catch (e) { - assert.dom('.rose-dialog').isVisible(); - await click( - '.rose-dialog-footer button:first-of-type', - 'Click Discard', - ); + assert.dom(commonSelectors.MODAL_WARNING).isVisible(); + await click(commonSelectors.MODAL_WARNING_CONFIRM_BTN, 'Click Discard'); assert.strictEqual(currentURL(), urls.credentials); assert.notEqual( this.server.schema.credentials.where({ type: 'username_password' }) @@ -349,11 +347,8 @@ module( try { await visit(urls.credentials); } catch (e) { - assert.dom('.rose-dialog').isVisible(); - await click( - '.rose-dialog-footer button:first-of-type', - 'Click Discard', - ); + assert.dom(commonSelectors.MODAL_WARNING).isVisible(); + await click(commonSelectors.MODAL_WARNING_CONFIRM_BTN, 'Click Discard'); assert.strictEqual(currentURL(), urls.credentials); assert.notEqual( this.server.schema.credentials.where({ type: 'ssh_private_key' }) @@ -376,11 +371,8 @@ module( try { await visit(urls.credentials); } catch (e) { - assert.dom('.rose-dialog').isVisible(); - await click( - '.rose-dialog-footer button:first-of-type', - 'Click Discard', - ); + assert.dom(commonSelectors.MODAL_WARNING).isVisible(); + await click(commonSelectors.MODAL_WARNING_CONFIRM_BTN, 'Click Discard'); assert.strictEqual(currentURL(), urls.credentials); assert.notEqual( this.server.schema.credentials.where({ type: 'json' }).models[0].name, @@ -403,8 +395,8 @@ module( try { await visit(urls.credentials); } catch (e) { - assert.dom('.rose-dialog').isVisible(); - await click('.rose-dialog-footer button:last-child', 'Click Cancel'); + assert.dom(commonSelectors.MODAL_WARNING).isVisible(); + await click(commonSelectors.MODAL_WARNING_CANCEL_BTN, 'Click Cancel'); assert.strictEqual(currentURL(), urls.usernamePasswordCredential); assert.strictEqual(find('[name="name"]').value, mockInput); assert.strictEqual( @@ -429,8 +421,8 @@ module( try { await visit(urls.credentials); } catch (e) { - assert.dom('.rose-dialog').isVisible(); - await click('.rose-dialog-footer button:last-child', 'Click Cancel'); + assert.dom(commonSelectors.MODAL_WARNING).isVisible(); + await click(commonSelectors.MODAL_WARNING_CANCEL_BTN, 'Click Cancel'); assert.strictEqual(currentURL(), urls.usernameKeyPairCredential); assert.strictEqual(find('[name="name"]').value, mockInput); assert.strictEqual( @@ -455,8 +447,8 @@ module( try { await visit(urls.credentials); } catch (e) { - assert.dom('.rose-dialog').isVisible(); - await click('.rose-dialog-footer button:last-child', 'Click Cancel'); + assert.dom(commonSelectors.MODAL_WARNING).isVisible(); + await click(commonSelectors.MODAL_WARNING_CANCEL_BTN, 'Click Cancel'); assert.strictEqual(currentURL(), urls.jsonCredential); assert.strictEqual(find('[name="name"]').value, mockInput); assert.strictEqual( diff --git a/ui/admin/tests/acceptance/credential-store/delete-test.js b/ui/admin/tests/acceptance/credential-store/delete-test.js index 28cf87763a..a4d234d840 100644 --- a/ui/admin/tests/acceptance/credential-store/delete-test.js +++ b/ui/admin/tests/acceptance/credential-store/delete-test.js @@ -5,7 +5,7 @@ import { module, test } from 'qunit'; import { visit, click } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import { authenticateSession } from 'ember-simple-auth/test-support'; @@ -42,7 +42,7 @@ module('Acceptance | credential-stores | delete', function (hooks) { vaultCredentialStore: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { @@ -77,7 +77,7 @@ module('Acceptance | credential-stores | delete', function (hooks) { return this.server.schema.credentialStores.where({ type: 'vault' }).models .length; }; - authenticateSession({}); + await authenticateSession({}); }); test('can delete credential store of type vault', async function (assert) { diff --git a/ui/admin/tests/acceptance/credential-store/list-test.js b/ui/admin/tests/acceptance/credential-store/list-test.js index e0ada11214..521c36d1b3 100644 --- a/ui/admin/tests/acceptance/credential-store/list-test.js +++ b/ui/admin/tests/acceptance/credential-store/list-test.js @@ -5,15 +5,10 @@ import { module, test } from 'qunit'; import { visit, click, fillIn, waitFor } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; import { TYPE_CREDENTIAL_STORE_STATIC, TYPE_CREDENTIAL_STORE_VAULT, @@ -49,7 +44,7 @@ module('Acceptance | credential-stores | list', function (hooks) { vaultCredentialStore: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', @@ -75,7 +70,7 @@ module('Acceptance | credential-stores | list', function (hooks) { const featuresService = this.owner.lookup('service:features'); featuresService.enable('static-credentials'); - authenticateSession({}); + await authenticateSession({}); }); test('users can navigate to credential-stores with proper authorization', async function (assert) { diff --git a/ui/admin/tests/acceptance/credential-store/read-test.js b/ui/admin/tests/acceptance/credential-store/read-test.js index 296b334df3..19e2d41982 100644 --- a/ui/admin/tests/acceptance/credential-store/read-test.js +++ b/ui/admin/tests/acceptance/credential-store/read-test.js @@ -5,11 +5,12 @@ import { module, test } from 'qunit'; import { visit, click, currentURL } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { authenticateSession } from 'ember-simple-auth/test-support'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; module('Acceptance | credential-stores | read', function (hooks) { setupApplicationTest(hooks); @@ -38,7 +39,7 @@ module('Acceptance | credential-stores | read', function (hooks) { unknownCredentialStore: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { @@ -66,7 +67,7 @@ module('Acceptance | credential-stores | read', function (hooks) { urls.vaultCredentialStore = `${urls.credentialStores}/${instances.vaultCredentialStore.id}`; urls.unknownCredentialStore = `${urls.credentialStores}/foo`; - authenticateSession({ username: 'admin' }); + await authenticateSession({ username: 'admin' }); featuresService = this.owner.lookup('service:features'); }); @@ -101,8 +102,12 @@ module('Acceptance | credential-stores | read', function (hooks) { await click(`[href="${urls.credentialStores}"]`); - assert.dom('.rose-table-body tr:first-child a').doesNotExist(); - assert.dom(`[href="${urls.vaultCredentialStore}"]`).exists(); + assert + .dom(commonSelectors.TABLE_RESOURCE_LINK(urls.vaultCredentialStore)) + .isVisible(); + assert + .dom(commonSelectors.TABLE_RESOURCE_LINK(urls.staticCredentialStore)) + .doesNotExist(); }); test('cannot navigate to a vault credential store form without proper authorization', async function (assert) { @@ -115,15 +120,21 @@ module('Acceptance | credential-stores | read', function (hooks) { await click(`[href="${urls.credentialStores}"]`); - assert.dom('.rose-table-body tr:nth-child(2) a').doesNotExist(); - assert.dom(`[href="${urls.staticCredentialStore}"]`).exists(); + assert + .dom(commonSelectors.TABLE_RESOURCE_LINK(urls.staticCredentialStore)) + .isVisible(); + assert + .dom(commonSelectors.TABLE_RESOURCE_LINK(urls.vaultCredentialStore)) + .doesNotExist(); }); test('visiting an unknown credential store displays 404 message', async function (assert) { await visit(urls.unknownCredentialStore); await a11yAudit(); - assert.dom('.rose-message-subtitle').hasText('Error 404'); + assert + .dom(commonSelectors.RESOURCE_NOT_FOUND_SUBTITLE) + .hasText(commonSelectors.RESOURCE_NOT_FOUND_VALUE); }); test('users can link to docs page for credential store', async function (assert) { @@ -138,7 +149,7 @@ module('Acceptance | credential-stores | read', function (hooks) { .exists(); }); - test('users can navigate to credential store and incorrect url autocorrects', async function (assert) { + test('users can navigate to credential store and incorrect url auto-corrects', async function (assert) { const projectScope = this.server.create('scope', { type: 'project', scope: { id: instances.scopes.org.id, type: 'org' }, diff --git a/ui/admin/tests/acceptance/credential-store/selectors.js b/ui/admin/tests/acceptance/credential-store/selectors.js new file mode 100644 index 0000000000..681714bf75 --- /dev/null +++ b/ui/admin/tests/acceptance/credential-store/selectors.js @@ -0,0 +1,18 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +// Manage actions dropdown selectors +export const MANAGE_DROPDOWN = + '[data-test-manage-credential-stores-dropdown] button:first-child'; +export const EDIT_ACTION = + '[data-test-manage-credential-stores-dropdown] ul li:nth-child(2) a'; + +export const CODE_EDITOR_BODY = '[data-test-code-editor-field-editor] textarea'; +export const CODE_BLOCK_BODY = '.hds-code-block__code'; + +export const TYPE_VAULT = '[value="vault"]'; + +export const FIELD_VAULT_ADDRESS_ERROR = + '[data-test-error-message-vault-address]'; diff --git a/ui/admin/tests/acceptance/credential-store/update-test.js b/ui/admin/tests/acceptance/credential-store/update-test.js index 4652bd23f7..08587005b5 100644 --- a/ui/admin/tests/acceptance/credential-store/update-test.js +++ b/ui/admin/tests/acceptance/credential-store/update-test.js @@ -5,11 +5,13 @@ import { module, test } from 'qunit'; import { visit, currentURL, find, click, fillIn } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import { authenticateSession } from 'ember-simple-auth/test-support'; import { Response } from 'miragejs'; +import * as selectors from './selectors'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; module('Acceptance | credential-stores | update', function (hooks) { setupApplicationTest(hooks); @@ -33,7 +35,7 @@ module('Acceptance | credential-stores | update', function (hooks) { vaultCredentialStore: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { @@ -59,7 +61,13 @@ module('Acceptance | credential-stores | update', function (hooks) { urls.credentialStores = `${urls.projectScope}/credential-stores`; urls.vaultCredentialStore = `${urls.credentialStores}/${instances.vaultCredentialStore.id}`; urls.staticCredentialStore = `${urls.credentialStores}/${instances.staticCredentialStore.id}`; - authenticateSession({}); + urls.workerFilter = `${urls.credentialStores}/${instances.vaultCredentialStore.id}/worker-filter`; + urls.editWorkerFilter = `${urls.credentialStores}/${instances.vaultCredentialStore.id}/edit-worker-filter`; + await authenticateSession({}); + + // Enable feature flag + const featuresService = this.owner.lookup('service:features'); + featuresService.enable('vault-worker-filter'); }); test('can save changes to existing static credential store', async function (assert) { @@ -212,8 +220,8 @@ module('Acceptance | credential-stores | update', function (hooks) { try { await visit(urls.credentialStores); } catch (e) { - assert.ok(find('.rose-dialog')); - await click('.rose-dialog-footer button:first-child', 'Click Discard'); + assert.ok(find(commonSelectors.MODAL_WARNING)); + await click(commonSelectors.MODAL_WARNING_CONFIRM_BTN, 'Click Discard'); assert.strictEqual(currentURL(), urls.credentialStores); assert.notEqual( this.server.schema.credentialStores.where({ type: 'static' }).models[0] @@ -237,8 +245,8 @@ module('Acceptance | credential-stores | update', function (hooks) { try { await visit(urls.credentialStores); } catch (e) { - assert.ok(find('.rose-dialog')); - await click('.rose-dialog-footer button:first-child', 'Click Discard'); + assert.ok(find(commonSelectors.MODAL_WARNING)); + await click(commonSelectors.MODAL_WARNING_CONFIRM_BTN, 'Click Discard'); assert.strictEqual(currentURL(), urls.credentialStores); assert.notEqual( this.server.schema.credentialStores.where({ type: 'vault' }).models[0] @@ -261,8 +269,8 @@ module('Acceptance | credential-stores | update', function (hooks) { try { await visit(urls.credentialStores); } catch (e) { - assert.ok(find('.rose-dialog')); - await click('.rose-dialog-footer button:last-child'); + assert.ok(find(commonSelectors.MODAL_WARNING)); + await click(commonSelectors.MODAL_WARNING_CANCEL_BTN); assert.strictEqual(currentURL(), urls.staticCredentialStore); assert.notEqual( this.server.schema.credentialStores.where({ type: 'static' }).models[0] @@ -285,8 +293,8 @@ module('Acceptance | credential-stores | update', function (hooks) { try { await visit(urls.credentialStores); } catch (e) { - assert.ok(find('.rose-dialog')); - await click('.rose-dialog-footer button:last-child'); + assert.ok(find(commonSelectors.MODAL_WARNING)); + await click(commonSelectors.MODAL_WARNING_CANCEL_BTN); assert.strictEqual(currentURL(), urls.vaultCredentialStore); assert.notEqual( this.server.schema.credentialStores.where({ type: 'vault' }).models[0] @@ -295,4 +303,63 @@ module('Acceptance | credential-stores | update', function (hooks) { ); } }); + + test('visiting static credential store does not show worker filter tab', async function (assert) { + await visit(urls.staticCredentialStore); + + assert.dom(commonSelectors.HREF(urls.workerFilter)).doesNotExist(); + }); + + test('user can click vault credential store worker filter tab and be rerouted to correct url', async function (assert) { + await visit(urls.vaultCredentialStore); + + assert.dom(commonSelectors.HREF(urls.workerFilter)).exists(); + + await click(commonSelectors.HREF(urls.workerFilter)); + + assert.strictEqual(currentURL(), urls.workerFilter); + }); + + test('manage actions dropdown displays edit option and routes to correct url', async function (assert) { + await visit(urls.vaultCredentialStore); + + await click(commonSelectors.HREF(urls.workerFilter)); + await click(selectors.MANAGE_DROPDOWN); + await click(selectors.EDIT_ACTION); + + assert.strictEqual(currentURL(), urls.editWorkerFilter); + }); + + test('when work filters code editor is empty, save btn reroutes to empty state template', async function (assert) { + instances.vaultCredentialStore.update({ + attributes: { worker_filter: null }, + }); + await visit(urls.vaultCredentialStore); + + await click(commonSelectors.HREF(urls.workerFilter)); + + assert.dom('.hds-application-state').exists(); + assert + .dom('.hds-application-state') + .hasText( + `No worker filter added You haven't added a worker filter yet. Add Worker Filter`, + ); + }); + + test('when worker filter exists, readonly code block displays the filter text', async function (assert) { + instances.vaultCredentialStore.update({ + attributes: { worker_filter: null }, + }); + + await visit(urls.vaultCredentialStore); + + await click(commonSelectors.HREF(urls.workerFilter)); + await click(selectors.MANAGE_DROPDOWN); + await click(selectors.EDIT_ACTION); + await fillIn(selectors.CODE_EDITOR_BODY, '"bar" in "/tags/foo"'); + await click(commonSelectors.SAVE_BTN); + + assert.dom(selectors.CODE_BLOCK_BODY).exists(); + assert.dom(selectors.CODE_BLOCK_BODY).includesText('"bar" in "/tags/foo"'); + }); }); diff --git a/ui/admin/tests/acceptance/groups/create-test.js b/ui/admin/tests/acceptance/groups/create-test.js index a68bd68fd8..6d226d7b87 100644 --- a/ui/admin/tests/acceptance/groups/create-test.js +++ b/ui/admin/tests/acceptance/groups/create-test.js @@ -5,7 +5,7 @@ import { module, test } from 'qunit'; import { visit, currentURL, click, fillIn } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import { Response } from 'miragejs'; @@ -31,8 +31,8 @@ module('Acceptance | groups | create', function (hooks) { newGroup: null, }; - hooks.beforeEach(function () { - authenticateSession({}); + hooks.beforeEach(async function () { + await authenticateSession({}); instances.scopes.org = this.server.create( 'scope', { diff --git a/ui/admin/tests/acceptance/groups/delete-test.js b/ui/admin/tests/acceptance/groups/delete-test.js index 05a2591cb9..ef28b75bd4 100644 --- a/ui/admin/tests/acceptance/groups/delete-test.js +++ b/ui/admin/tests/acceptance/groups/delete-test.js @@ -5,15 +5,10 @@ import { module, test } from 'qunit'; import { visit, click, find } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { Response } from 'miragejs'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; module('Acceptance | groups | delete', function (hooks) { setupApplicationTest(hooks); @@ -38,8 +33,8 @@ module('Acceptance | groups | delete', function (hooks) { newGroup: null, }; - hooks.beforeEach(function () { - authenticateSession({}); + hooks.beforeEach(async function () { + await authenticateSession({}); instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', diff --git a/ui/admin/tests/acceptance/groups/list-test.js b/ui/admin/tests/acceptance/groups/list-test.js index 29d9131ebd..07e49d60d5 100644 --- a/ui/admin/tests/acceptance/groups/list-test.js +++ b/ui/admin/tests/acceptance/groups/list-test.js @@ -5,15 +5,10 @@ import { module, test } from 'qunit'; import { visit, click, fillIn, waitUntil, findAll } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; import * as commonSelectors from 'admin/tests/helpers/selectors'; module('Acceptance | groups | list', function (hooks) { @@ -40,7 +35,7 @@ module('Acceptance | groups | list', function (hooks) { group2: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create( 'scope', @@ -61,7 +56,7 @@ module('Acceptance | groups | list', function (hooks) { urls.groups = `/scopes/${instances.scopes.org.id}/groups`; urls.group1 = `${urls.groups}/${instances.group1.id}`; urls.group2 = `${urls.groups}/${instances.group2.id}`; - authenticateSession({}); + await authenticateSession({}); }); test('can navigate to groups with proper authorization', async function (assert) { diff --git a/ui/admin/tests/acceptance/groups/members-test.js b/ui/admin/tests/acceptance/groups/members-test.js index 6fde451023..8feda962e0 100644 --- a/ui/admin/tests/acceptance/groups/members-test.js +++ b/ui/admin/tests/acceptance/groups/members-test.js @@ -5,17 +5,12 @@ import { module, test } from 'qunit'; import { visit, currentURL, click, find, findAll } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { Response } from 'miragejs'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; module('Acceptance | groups | members', function (hooks) { setupApplicationTest(hooks); @@ -42,8 +37,8 @@ module('Acceptance | groups | members', function (hooks) { const ADD_MEMBERS_ACTION_SELECTOR = '[data-test-manage-group-dropdown] ul li a'; - hooks.beforeEach(function () { - authenticateSession({ username: 'admin' }); + hooks.beforeEach(async function () { + await authenticateSession({ username: 'admin' }); instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', diff --git a/ui/admin/tests/acceptance/groups/read-test.js b/ui/admin/tests/acceptance/groups/read-test.js index f40302d16a..e74ee74680 100644 --- a/ui/admin/tests/acceptance/groups/read-test.js +++ b/ui/admin/tests/acceptance/groups/read-test.js @@ -5,20 +5,17 @@ import { module, test } from 'qunit'; import { visit, currentURL } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; +import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; import * as commonSelectors from 'admin/tests/helpers/selectors'; module('Acceptance | groups | read', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); + setupIndexedDb(hooks); const instances = { scopes: { @@ -34,8 +31,8 @@ module('Acceptance | groups | read', function (hooks) { newGroup: null, }; - hooks.beforeEach(function () { - authenticateSession({ username: 'admin' }); + hooks.beforeEach(async function () { + await authenticateSession({ username: 'admin' }); instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', @@ -59,12 +56,12 @@ module('Acceptance | groups | read', function (hooks) { test('cannot navigate to a group form without proper authorization', async function (assert) { instances.group.authorized_actions = instances.group.authorized_actions.filter((item) => item !== 'read'); - await visit(urls.group); + await visit(urls.groups); - assert.dom(commonSelectors.TABLE_FIRST_ROW_RESOURCE_LINK).doesNotExist(); + assert.dom(commonSelectors.TABLE_RESOURCE_LINK(urls.group)).doesNotExist(); }); - test('users can navigate to group and incorrect url autocorrects', async function (assert) { + test('users can navigate to group and incorrect url auto-corrects', async function (assert) { const orgScope = this.server.create('scope', { type: 'org', scope: { id: 'global', type: 'global' }, diff --git a/ui/admin/tests/acceptance/groups/update-test.js b/ui/admin/tests/acceptance/groups/update-test.js index f19cb73a33..3fb55c9702 100644 --- a/ui/admin/tests/acceptance/groups/update-test.js +++ b/ui/admin/tests/acceptance/groups/update-test.js @@ -5,15 +5,10 @@ import { module, test } from 'qunit'; import { visit, currentURL, click, find, fillIn } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { Response } from 'miragejs'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; module('Acceptance | groups | update', function (hooks) { setupApplicationTest(hooks); @@ -33,8 +28,8 @@ module('Acceptance | groups | update', function (hooks) { newGroup: null, }; - hooks.beforeEach(function () { - authenticateSession({}); + hooks.beforeEach(async function () { + await authenticateSession({}); instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', diff --git a/ui/admin/tests/acceptance/host-catalogs/create-test.js b/ui/admin/tests/acceptance/host-catalogs/create-test.js index 723ef8cfb3..75a93c8c55 100644 --- a/ui/admin/tests/acceptance/host-catalogs/create-test.js +++ b/ui/admin/tests/acceptance/host-catalogs/create-test.js @@ -5,15 +5,10 @@ import { module, test } from 'qunit'; import { visit, currentURL, click, fillIn } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { Response } from 'miragejs'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; module('Acceptance | host-catalogs | create', function (hooks) { @@ -26,11 +21,12 @@ module('Acceptance | host-catalogs | create', function (hooks) { const NAME_INPUT_SELECTOR = '[name="name"]'; const DESCRIPTION_INPUT_SELECTOR = '[name="description"]'; - const TYPE_INPUT_SELECTOR = '[name="type"]'; + const TYPE_INPUT_SELECTOR = '[name="Type"]'; const SAVE_BUTTON_SELECTOR = '[type="submit"]'; const CANCEL_BUTTON_SELECTOR = '.rose-form-actions [type="button"]'; const ALERT_TEXT_SELECTOR = '[data-test-toast-notification] .hds-alert__description'; + const WORKER_FILTER_INPUT_SELECTOR = '[name=worker_filter]'; const instances = { scopes: { @@ -50,9 +46,10 @@ module('Acceptance | host-catalogs | create', function (hooks) { newStaticHostCatalog: null, newAWSDynamicHostCatalog: null, newAzureDynamicHostCatalog: null, + newGCPDynamicHostCatalog: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.orgScope = this.server.create( @@ -86,13 +83,14 @@ module('Acceptance | host-catalogs | create', function (hooks) { urls.newStaticHostCatalog = `${urls.newHostCatalog}?type=static`; urls.newAWSDynamicHostCatalog = `${urls.newHostCatalog}?type=aws`; urls.newAzureDynamicHostCatalog = `${urls.newHostCatalog}?type=azure`; + urls.newGCPDynamicHostCatalog = `${urls.newHostCatalog}?type=gcp`; featuresService = this.owner.lookup('service:features'); // Generate resource counter getHostCatalogCount = () => this.server.schema.hostCatalogs.all().models.length; - authenticateSession({}); + await authenticateSession({}); }); test('Users can create new static host catalogs', async function (assert) { @@ -115,7 +113,7 @@ module('Acceptance | host-catalogs | create', function (hooks) { assert.strictEqual(getHostCatalogCount(), count + 1); }); - test('Users can create new dynamic aws host catalogs with azure provider', async function (assert) { + test('Users can create new dynamic azure host catalogs with azure provider', async function (assert) { const count = getHostCatalogCount(); await visit(urls.newAzureDynamicHostCatalog); await fillIn(NAME_INPUT_SELECTOR, 'random string'); @@ -125,6 +123,18 @@ module('Acceptance | host-catalogs | create', function (hooks) { assert.strictEqual(getHostCatalogCount(), count + 1); }); + test('Users can create new dynamic host catalogs with GCP provider ', async function (assert) { + const count = getHostCatalogCount(); + await visit(urls.newGCPDynamicHostCatalog); + await fillIn(NAME_INPUT_SELECTOR, 'random string'); + await fillIn(DESCRIPTION_INPUT_SELECTOR, 'random string'); + await fillIn('[name="zone"]', 'random string'); + await fillIn('[name="project_id"]', 'random string'); + await fillIn('[name="client_email"]', 'random string'); + await click(SAVE_BUTTON_SELECTOR); + assert.strictEqual(getHostCatalogCount(), count + 1); + }); + test('Users can cancel creation of new static host catalogs', async function (assert) { const count = getHostCatalogCount(); await visit(urls.newStaticHostCatalog); @@ -143,6 +153,15 @@ module('Acceptance | host-catalogs | create', function (hooks) { assert.strictEqual(getHostCatalogCount(), count); }); + test('Users can cancel creation of new dynamic host catalogs with GCP provider', async function (assert) { + const count = getHostCatalogCount(); + await visit(urls.newGCPDynamicHostCatalog); + await fillIn(NAME_INPUT_SELECTOR, 'random string'); + await click(CANCEL_BUTTON_SELECTOR); + assert.strictEqual(currentURL(), urls.hostCatalogs); + assert.strictEqual(getHostCatalogCount(), count); + }); + test('Users can cancel creation of new dynamic host catalogs with Azure provider', async function (assert) { const count = getHostCatalogCount(); await visit(urls.newAzureDynamicHostCatalog); @@ -159,7 +178,7 @@ module('Acceptance | host-catalogs | create', function (hooks) { 'host-catalogs' ].includes('create'), ); - assert.dom(`[href="${urls.newHostCatalog}"]`).exists(); + assert.dom(`[href="${urls.newHostCatalog}"]`).isVisible(); }); test('Users cannot navigate to new static host catalogs route without proper authorization', async function (assert) { @@ -171,7 +190,7 @@ module('Acceptance | host-catalogs | create', function (hooks) { 'host-catalogs' ].includes('create'), ); - assert.dom(`[href="${urls.newStaticHostCatalog}"]`).doesNotExist(); + assert.dom(`[href="${urls.newStaticHostCatalog}"]`).isNotVisible(); }); test('saving a new static host catalog with invalid fields displays error messages', async function (assert) { @@ -200,15 +219,26 @@ module('Acceptance | host-catalogs | create', function (hooks) { assert.dom('[data-test-error-message-name]').hasText('Name is required.'); }); - test('users should not see worker filter field in community edition', async function (assert) { + test('users should not see worker filter field in community edition when AWS host catalog is selected', async function (assert) { + await visit(urls.newAWSDynamicHostCatalog); + assert.dom(WORKER_FILTER_INPUT_SELECTOR).isNotVisible(); + }); + + test('users should not see worker filter field in community edition when GCP host catalog is selected', async function (assert) { + await visit(urls.newGCPDynamicHostCatalog); + assert.dom(WORKER_FILTER_INPUT_SELECTOR).isNotVisible(); + }); + + test('users should see worker filter field in enterprise edition when AWS host catalog is selected', async function (assert) { + featuresService.enable('host-catalog-worker-filter'); await visit(urls.newAWSDynamicHostCatalog); - assert.dom('[data-test-dynamic-credential-worker-filter]').doesNotExist(); + assert.dom(WORKER_FILTER_INPUT_SELECTOR).isVisible(); }); - test('users should see worker filter field in enterprise edition', async function (assert) { - featuresService.enable('dynamic-credentials-worker-filter'); + test('users should see worker filter field in enterprise edition when GCP host catalog is selected', async function (assert) { + featuresService.enable('host-catalog-worker-filter'); await visit(urls.newAWSDynamicHostCatalog); - assert.dom('[data-test-dynamic-credential-worker-filter]').exists(); + assert.dom(WORKER_FILTER_INPUT_SELECTOR).isVisible(); }); test('users cannot directly navigate to new host catalog route without proper authorization', async function (assert) { diff --git a/ui/admin/tests/acceptance/host-catalogs/delete-test.js b/ui/admin/tests/acceptance/host-catalogs/delete-test.js index b043430460..2bab43b5cf 100644 --- a/ui/admin/tests/acceptance/host-catalogs/delete-test.js +++ b/ui/admin/tests/acceptance/host-catalogs/delete-test.js @@ -5,16 +5,16 @@ import { module, test } from 'qunit'; import { visit, click, currentURL } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { Response } from 'miragejs'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; +import { + TYPE_HOST_CATALOG_DYNAMIC, + TYPE_HOST_CATALOG_PLUGIN_GCP, +} from 'api/models/host-catalog'; module('Acceptance | host-catalogs | delete', function (hooks) { setupApplicationTest(hooks); @@ -34,6 +34,7 @@ module('Acceptance | host-catalogs | delete', function (hooks) { project: null, }, hostCatalog: null, + gcpDynamicHostCatalog: null, }; const urls = { globalScope: null, @@ -41,9 +42,10 @@ module('Acceptance | host-catalogs | delete', function (hooks) { projectScope: null, hostCatalogs: null, hostCatalog: null, + gcpDynamicHostCatalog: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { @@ -57,16 +59,24 @@ module('Acceptance | host-catalogs | delete', function (hooks) { instances.hostCatalog = this.server.create('host-catalog', { scope: instances.scopes.project, }); + + instances.gcpDynamicHostCatalog = this.server.create('host-catalog', { + scope: instances.scopes.project, + type: TYPE_HOST_CATALOG_DYNAMIC, + plugin: { name: TYPE_HOST_CATALOG_PLUGIN_GCP }, + }); + // Generate route URLs for resources urls.globalScope = `/scopes/global/scopes`; urls.orgScope = `/scopes/${instances.scopes.org.id}/scopes`; urls.projectScope = `/scopes/${instances.scopes.project.id}`; urls.hostCatalogs = `${urls.projectScope}/host-catalogs`; urls.hostCatalog = `${urls.hostCatalogs}/${instances.hostCatalog.id}`; + urls.gcpDynamicHostCatalog = `${urls.hostCatalogs}/${instances.gcpDynamicHostCatalog.id}`; // Generate resource counter getHostCatalogCount = () => this.server.schema.hostCatalogs.all().models.length; - authenticateSession({}); + await authenticateSession({}); }); test('can delete host catalog', async function (assert) { @@ -80,6 +90,18 @@ module('Acceptance | host-catalogs | delete', function (hooks) { assert.strictEqual(getHostCatalogCount(), hostCatalogCount - 1); }); + test('can delete GCP host catalog', async function (assert) { + const hostCatalogCount = getHostCatalogCount(); + + await visit(urls.hostCatalogs); + await click(`[href="${urls.gcpDynamicHostCatalog}"]`); + + await click(MANAGE_DROPDOWN_SELECTOR); + await click(DELETE_ACTION_SELECTOR); + + assert.strictEqual(getHostCatalogCount(), hostCatalogCount - 1); + }); + test('cannot delete host catalog without proper authorization', async function (assert) { await visit(urls.hostCatalogs); instances.hostCatalog.authorized_actions = @@ -102,7 +124,7 @@ module('Acceptance | host-catalogs | delete', function (hooks) { await click(`[href="${urls.hostCatalog}"]`); await click(MANAGE_DROPDOWN_SELECTOR); await click(DELETE_ACTION_SELECTOR); - await click('.rose-dialog .rose-button-primary'); + await click(commonSelectors.MODAL_WARNING_CONFIRM_BTN); assert .dom('[data-test-toast-notification] .hds-alert__description') @@ -120,7 +142,7 @@ module('Acceptance | host-catalogs | delete', function (hooks) { await click(`[href="${urls.hostCatalog}"]`); await click(MANAGE_DROPDOWN_SELECTOR); await click(DELETE_ACTION_SELECTOR); - await click('.rose-dialog .rose-button-secondary'); + await click(commonSelectors.MODAL_WARNING_CANCEL_BTN); assert.strictEqual(getHostCatalogCount(), hostCatalogCount); assert.strictEqual(currentURL(), urls.hostCatalog); diff --git a/ui/admin/tests/acceptance/host-catalogs/host-sets/create-test.js b/ui/admin/tests/acceptance/host-catalogs/host-sets/create-test.js index 83e7db51b7..1d3b9553a3 100644 --- a/ui/admin/tests/acceptance/host-catalogs/host-sets/create-test.js +++ b/ui/admin/tests/acceptance/host-catalogs/host-sets/create-test.js @@ -5,15 +5,10 @@ import { module, test } from 'qunit'; import { visit, currentURL, find, click, fillIn } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { Response } from 'miragejs'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; module('Acceptance | host-catalogs | host sets | create', function (hooks) { setupApplicationTest(hooks); @@ -54,7 +49,7 @@ module('Acceptance | host-catalogs | host sets | create', function (hooks) { newHostSet: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { @@ -84,7 +79,7 @@ module('Acceptance | host-catalogs | host sets | create', function (hooks) { urls.newHostSet = `${urls.hostSets}/new`; // Generate resource couner getHostSetCount = () => this.server.schema.hostSets.all().models.length; - authenticateSession({}); + await authenticateSession({}); }); test('can create new host sets', async function (assert) { @@ -155,6 +150,37 @@ module('Acceptance | host-catalogs | host sets | create', function (hooks) { assert.deepEqual(hostSet.syncIntervalSeconds, 10); }); + test('can create new gcp host set', async function (assert) { + instances.hostCatalog = this.server.create('host-catalog', { + scope: instances.scopes.project, + type: 'plugin', + plugin: { + id: `plugin-id-1`, + name: 'gcp', + }, + }); + + const count = getHostSetCount(); + await visit( + `${urls.hostCatalogs}/${instances.hostCatalog.id}/host-sets/new`, + ); + const name = 'gcp host set'; + await fillIn(NAME_SELECTOR, name); + await fillIn(PREFERRED_ENDPOINT_TEXT_INPUT_SELECTOR, 'endpoint'); + await click(PREFERRED_ENDPOINT_BUTTON_SELECTOR); + await fillIn(FILTER_TEXT_INPUT_SELECTOR, 'filter_test'); + await click(FILTER_BUTTON_SELECTOR); + + await fillIn(SYNC_INTERVAL_SELECTOR, 10); + await click(SUBMIT_BTN_SELECTOR); + + assert.strictEqual(getHostSetCount(), count + 1); + const hostSet = this.server.schema.hostSets.findBy({ name }); + assert.deepEqual(hostSet.preferredEndpoints, ['endpoint']); + assert.deepEqual(hostSet.attributes.filters, ['filter_test']); + assert.deepEqual(hostSet.syncIntervalSeconds, 10); + }); + test('Users cannot create a new host set without proper authorization', async function (assert) { instances.hostCatalog.authorized_collection_actions['host-sets'] = []; await visit(urls.hostCatalog); diff --git a/ui/admin/tests/acceptance/host-catalogs/host-sets/delete-test.js b/ui/admin/tests/acceptance/host-catalogs/host-sets/delete-test.js index fa17fc1ce5..82529926b8 100644 --- a/ui/admin/tests/acceptance/host-catalogs/host-sets/delete-test.js +++ b/ui/admin/tests/acceptance/host-catalogs/host-sets/delete-test.js @@ -5,17 +5,12 @@ import { module, test } from 'qunit'; import { visit, find, click } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { Response } from 'miragejs'; import { resolve, reject } from 'rsvp'; import sinon from 'sinon'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; module('Acceptance | host-catalogs | host sets | delete', function (hooks) { setupApplicationTest(hooks); @@ -48,7 +43,7 @@ module('Acceptance | host-catalogs | host sets | delete', function (hooks) { newHostSet: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { @@ -78,7 +73,7 @@ module('Acceptance | host-catalogs | host sets | delete', function (hooks) { urls.newHostSet = `${urls.hostSets}/new`; // Generate resource couner getHostSetCount = () => this.server.schema.hostSets.all().models.length; - authenticateSession({}); + await authenticateSession({}); }); test('can delete host', async function (assert) { diff --git a/ui/admin/tests/acceptance/host-catalogs/host-sets/hosts-test.js b/ui/admin/tests/acceptance/host-catalogs/host-sets/hosts-test.js index d33f750985..19fbd677d0 100644 --- a/ui/admin/tests/acceptance/host-catalogs/host-sets/hosts-test.js +++ b/ui/admin/tests/acceptance/host-catalogs/host-sets/hosts-test.js @@ -12,16 +12,11 @@ import { findAll, fillIn, } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { Response } from 'miragejs'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; module('Acceptance | host-catalogs | host-sets | hosts', function (hooks) { setupApplicationTest(hooks); @@ -57,7 +52,7 @@ module('Acceptance | host-catalogs | host-sets | hosts', function (hooks) { newHost: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { @@ -90,7 +85,7 @@ module('Acceptance | host-catalogs | host-sets | hosts', function (hooks) { // Generate resource counter getHostSetHostCount = () => this.server.schema.hostSets.all().models[0].hosts.length; - authenticateSession({ username: 'admin' }); + await authenticateSession({ username: 'admin' }); }); test('visiting host set hosts', async function (assert) { diff --git a/ui/admin/tests/acceptance/host-catalogs/host-sets/list-test.js b/ui/admin/tests/acceptance/host-catalogs/host-sets/list-test.js index 24a5f2d87c..54eda5971c 100644 --- a/ui/admin/tests/acceptance/host-catalogs/host-sets/list-test.js +++ b/ui/admin/tests/acceptance/host-catalogs/host-sets/list-test.js @@ -5,14 +5,9 @@ import { module, test } from 'qunit'; import { visit, find } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; module('Acceptance | host-catalogs | host sets | list', function (hooks) { setupApplicationTest(hooks); @@ -41,7 +36,7 @@ module('Acceptance | host-catalogs | host sets | list', function (hooks) { newHostSet: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { @@ -68,7 +63,7 @@ module('Acceptance | host-catalogs | host sets | list', function (hooks) { urls.hostSets = `${urls.hostCatalog}/host-sets`; urls.hostSet = `${urls.hostSets}/${instances.hostSet.id}`; urls.newHostSet = `${urls.hostSets}/new`; - authenticateSession({}); + await authenticateSession({}); }); test('Users can navigate to host-sets with proper authorization', async function (assert) { diff --git a/ui/admin/tests/acceptance/host-catalogs/host-sets/read-test.js b/ui/admin/tests/acceptance/host-catalogs/host-sets/read-test.js index 6b464d8511..7778639695 100644 --- a/ui/admin/tests/acceptance/host-catalogs/host-sets/read-test.js +++ b/ui/admin/tests/acceptance/host-catalogs/host-sets/read-test.js @@ -5,15 +5,11 @@ import { module, test } from 'qunit'; import { visit, currentURL, click } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; module('Acceptance | host-catalogs | host-sets | read', function (hooks) { setupApplicationTest(hooks); @@ -40,7 +36,7 @@ module('Acceptance | host-catalogs | host-sets | read', function (hooks) { unknownHostSet: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { @@ -68,7 +64,7 @@ module('Acceptance | host-catalogs | host-sets | read', function (hooks) { urls.hostSet = `${urls.hostSets}/${instances.hostSet.id}`; urls.unknownHostSet = `${urls.hostSets}/foo`; - authenticateSession({ username: 'admin' }); + await authenticateSession({ username: 'admin' }); }); test('visiting host sets', async function (assert) { @@ -89,14 +85,18 @@ module('Acceptance | host-catalogs | host-sets | read', function (hooks) { await click(`[href="${urls.hostSets}"]`); - assert.dom('.rose-table-body tr:first-child a').doesNotExist(); + assert + .dom(commonSelectors.TABLE_RESOURCE_LINK(urls.hostSet)) + .doesNotExist(); }); test('visiting an unknown host set displays 404 message', async function (assert) { await visit(urls.unknownHostSet); await a11yAudit(); - assert.dom('.rose-message-subtitle').hasText('Error 404'); + assert + .dom(commonSelectors.RESOURCE_NOT_FOUND_SUBTITLE) + .hasText(commonSelectors.RESOURCE_NOT_FOUND_VALUE); }); test('users can link to docs page for host sets', async function (assert) { @@ -111,7 +111,7 @@ module('Acceptance | host-catalogs | host-sets | read', function (hooks) { .exists(); }); - test('users can navigate to host set and incorrect url autocorrects', async function (assert) { + test('users can navigate to host set and incorrect url auto-corrects', async function (assert) { const hostCatalog = this.server.create('host-catalog', { scope: instances.scopes.project, type: 'static', diff --git a/ui/admin/tests/acceptance/host-catalogs/host-sets/update-test.js b/ui/admin/tests/acceptance/host-catalogs/host-sets/update-test.js index 10da34f497..4ea2960823 100644 --- a/ui/admin/tests/acceptance/host-catalogs/host-sets/update-test.js +++ b/ui/admin/tests/acceptance/host-catalogs/host-sets/update-test.js @@ -12,15 +12,11 @@ import { click, fillIn, } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { Response } from 'miragejs'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; module('Acceptance | host-catalogs | host sets | update', function (hooks) { setupApplicationTest(hooks); @@ -67,7 +63,7 @@ module('Acceptance | host-catalogs | host sets | update', function (hooks) { azureHostSet: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { @@ -124,7 +120,7 @@ module('Acceptance | host-catalogs | host sets | update', function (hooks) { urls.awshostSet = `${urls.hostCatalogs}/${instances.awsHostCatalog.id}/host-sets/${instances.awsHostSet.id}`; urls.azureHostSet = `${urls.hostCatalogs}/${instances.azureHostCatalog.id}/host-sets/${instances.azureHostSet.id}`; // Generate resource couner - authenticateSession({}); + await authenticateSession({}); }); test('saving a new host set with invalid fields displays error messages', async function (assert) { @@ -251,6 +247,52 @@ module('Acceptance | host-catalogs | host sets | update', function (hooks) { assert.deepEqual(hostSet.syncIntervalSeconds, 10); }); + test('can save changes to an existing gcp host-set', async function (assert) { + await visit(urls.awshostSet); + + await click(EDIT_BUTTON_SELECTOR, 'Activate edit mode'); + + const name = 'gcp host set'; + await fillIn('[name="name"]', name); + + const endpointList = findAll(PREFERRED_ENDPOINT_REMOVE_BUTTON_SELECTOR); + + for (const element of endpointList) { + await click(element); + } + + assert.strictEqual( + findAll(PREFERRED_ENDPOINT_REMOVE_BUTTON_SELECTOR).length, + 0, + ); + + await fillIn(PREFERRED_ENDPOINT_TEXT_INPUT_SELECTOR, 'sample endpoint'); + await click(PREFERRED_ENDPOINT_BUTTON_SELECTOR); + + // Remove all the filters + const filterList = await Promise.all( + findAll(FILTER_REMOVE_BUTTON_SELECTOR), + ); + for (const element of filterList) { + await click(element); + } + + assert.strictEqual(findAll(FILTER_REMOVE_BUTTON_SELECTOR).length, 0); + await fillIn(FILTER_TEXT_INPUT_SELECTOR, 'sample filters'); + await click(FILTER_BUTTON_SELECTOR); + + await fillIn(SYNC_INTERVAL_SELECTOR, 10); + + await click(SUBMIT_BTN_SELECTOR); + + assert.strictEqual(currentURL(), urls.awshostSet); + const hostSet = this.server.schema.hostSets.findBy({ name }); + assert.strictEqual(hostSet.name, name); + assert.deepEqual(hostSet.preferredEndpoints, ['sample endpoint']); + assert.deepEqual(hostSet.attributes.filters, ['sample filters']); + assert.deepEqual(hostSet.syncIntervalSeconds, 10); + }); + test('cannot make changes to an existing host without proper authorization', async function (assert) { instances.hostSet.authorized_actions = instances.hostSet.authorized_actions.filter((item) => item !== 'update'); @@ -312,8 +354,8 @@ module('Acceptance | host-catalogs | host sets | update', function (hooks) { try { await visit(urls.hostSets); } catch (e) { - assert.ok(find('.rose-dialog')); - await click('.rose-dialog-footer button:first-child'); + assert.ok(find(commonSelectors.MODAL_WARNING)); + await click(commonSelectors.MODAL_WARNING_CONFIRM_BTN); assert.strictEqual(currentURL(), urls.hostSets); assert.notEqual( this.server.schema.hostSets.first().name, diff --git a/ui/admin/tests/acceptance/host-catalogs/hosts/create-test.js b/ui/admin/tests/acceptance/host-catalogs/hosts/create-test.js index e6c872f965..d5158b74ff 100644 --- a/ui/admin/tests/acceptance/host-catalogs/hosts/create-test.js +++ b/ui/admin/tests/acceptance/host-catalogs/hosts/create-test.js @@ -5,7 +5,7 @@ import { module, test } from 'qunit'; import { visit, currentURL, click, fillIn } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { Response } from 'miragejs'; import { authenticateSession } from 'ember-simple-auth/test-support'; @@ -39,7 +39,7 @@ module('Acceptance | host-catalogs | hosts | create', function (hooks) { newHost: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { @@ -70,7 +70,7 @@ module('Acceptance | host-catalogs | hosts | create', function (hooks) { urls.newHost = `${urls.hosts}/new`; // Generate resource couner getHostCount = () => this.server.schema.hosts.all().models.length; - authenticateSession({}); + await authenticateSession({}); }); test('can create new host', async function (assert) { @@ -98,14 +98,22 @@ module('Acceptance | host-catalogs | hosts | create', function (hooks) { assert.dom(commonSelectors.HREF(urls.newHost)).doesNotExist(); }); + test('Users can navigate to new host route with proper authorization', async function (assert) { await visit(urls.hosts); + assert.ok( instances.hostCatalog.authorized_collection_actions.hosts.includes( 'create', ), ); - assert.dom(selectors.MANAGE_DROPDOWN).exists(); + assert.dom(selectors.MANAGE_DROPDOWN_HOST_CATALOG).exists(); + await click(selectors.MANAGE_DROPDOWN_HOST_CATALOG); + assert + .dom(selectors.MANAGE_DROPDOWN_HOST_CATALOG_NEW_HOST) + .hasAttribute('href', urls.newHost); + await click(selectors.MANAGE_DROPDOWN_HOST_CATALOG_NEW_HOST); + assert.strictEqual(currentURL(), urls.newHost); }); test('Users cannot navigate to new host route without proper authorization', async function (assert) { @@ -141,8 +149,8 @@ module('Acceptance | host-catalogs | hosts | create', function (hooks) { details: { request_fields: [ { - name: 'name', - description: 'Name is required.', + name: 'address', + description: 'Address is required.', }, ], }, @@ -156,7 +164,7 @@ module('Acceptance | host-catalogs | hosts | create', function (hooks) { .dom(commonSelectors.ALERT_TOAST_BODY) .hasText('The request was invalid.'); - assert.dom(commonSelectors.FIELD_NAME_ERROR).hasText('Name is required.'); + assert.dom(selectors.FIELD_ADDRESS_ERROR).hasText('Address is required.'); }); test('users cannot directly navigate to new host route without proper authorization', async function (assert) { diff --git a/ui/admin/tests/acceptance/host-catalogs/hosts/delete-test.js b/ui/admin/tests/acceptance/host-catalogs/hosts/delete-test.js index 47db37aab2..670a3fcba0 100644 --- a/ui/admin/tests/acceptance/host-catalogs/hosts/delete-test.js +++ b/ui/admin/tests/acceptance/host-catalogs/hosts/delete-test.js @@ -4,28 +4,21 @@ */ import { module, test } from 'qunit'; -import { visit, find, click } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { visit, click } from '@ember/test-helpers'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { Response } from 'miragejs'; import { resolve, reject } from 'rsvp'; import sinon from 'sinon'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; +import * as selectors from './selectors'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; module('Acceptance | host-catalogs | hosts | delete', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); let getHostCount; - const MANAGE_DROPDOWN_SELECTOR = - '[data-test-manage-hosts-dropdown] button:first-child'; - const DELETE_ACTION_SELECTOR = - '[data-test-manage-hosts-dropdown] ul li button'; const instances = { scopes: { @@ -48,7 +41,7 @@ module('Acceptance | host-catalogs | hosts | delete', function (hooks) { newHost: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { @@ -78,14 +71,15 @@ module('Acceptance | host-catalogs | hosts | delete', function (hooks) { urls.newHost = `${urls.hosts}/new`; // Generate resource couner getHostCount = () => this.server.schema.hosts.all().models.length; - authenticateSession({}); + await authenticateSession({}); }); test('can delete host', async function (assert) { const count = getHostCount(); await visit(urls.host); - await click(MANAGE_DROPDOWN_SELECTOR); - await click(DELETE_ACTION_SELECTOR); + + await click(selectors.MANAGE_DROPDOWN_HOST); + await click(selectors.MANAGE_DROPDOWN_HOST_DELETE); assert.strictEqual(getHostCount(), count - 1); }); @@ -93,9 +87,8 @@ module('Acceptance | host-catalogs | hosts | delete', function (hooks) { instances.host.authorized_actions = instances.host.authorized_actions.filter((item) => item !== 'delete'); await visit(urls.host); - assert.notOk( - find('.rose-layout-page-actions .rose-dropdown-button-danger'), - ); + + assert.dom(selectors.MANAGE_DROPDOWN_HOST).doesNotExist(); }); test('can accept delete host via dialog', async function (assert) { @@ -104,8 +97,10 @@ module('Acceptance | host-catalogs | hosts | delete', function (hooks) { confirmService.confirm = sinon.fake.returns(resolve()); const count = getHostCount(); await visit(urls.host); - await click(MANAGE_DROPDOWN_SELECTOR); - await click(DELETE_ACTION_SELECTOR); + + await click(selectors.MANAGE_DROPDOWN_HOST); + await click(selectors.MANAGE_DROPDOWN_HOST_DELETE); + assert.strictEqual(getHostCount(), count - 1); assert.ok(confirmService.confirm.calledOnce); }); @@ -116,8 +111,10 @@ module('Acceptance | host-catalogs | hosts | delete', function (hooks) { confirmService.confirm = sinon.fake.returns(reject()); const count = getHostCount(); await visit(urls.host); - await click(MANAGE_DROPDOWN_SELECTOR); - await click(DELETE_ACTION_SELECTOR); + + await click(selectors.MANAGE_DROPDOWN_HOST); + await click(selectors.MANAGE_DROPDOWN_HOST_DELETE); + assert.strictEqual(getHostCount(), count); assert.ok(confirmService.confirm.calledOnce); }); @@ -135,10 +132,10 @@ module('Acceptance | host-catalogs | hosts | delete', function (hooks) { ); }); await visit(urls.host); - await click(MANAGE_DROPDOWN_SELECTOR); - await click(DELETE_ACTION_SELECTOR); - assert - .dom('[data-test-toast-notification] .hds-alert__description') - .hasText('Oops.'); + + await click(selectors.MANAGE_DROPDOWN_HOST); + await click(selectors.MANAGE_DROPDOWN_HOST_DELETE); + + assert.dom(commonSelectors.ALERT_TOAST_BODY).hasText('Oops.'); }); }); diff --git a/ui/admin/tests/acceptance/host-catalogs/hosts/list-test.js b/ui/admin/tests/acceptance/host-catalogs/hosts/list-test.js index ae7a135fd3..e56c4e09b9 100644 --- a/ui/admin/tests/acceptance/host-catalogs/hosts/list-test.js +++ b/ui/admin/tests/acceptance/host-catalogs/hosts/list-test.js @@ -4,25 +4,17 @@ */ import { module, test } from 'qunit'; -import { visit, find, click } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { visit, click, currentURL } from '@ember/test-helpers'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; +import * as selectors from './selectors'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; module('Acceptance | host-catalogs | hosts | list', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - const MANAGE_DROPDOWN_SELECTOR = - '[data-test-manage-host-catalogs-dropdown] button:first-child'; - const NEW_HOST_SELECTOR = - '[data-test-manage-host-catalogs-dropdown] div ul li a'; - const instances = { scopes: { global: null, @@ -44,7 +36,7 @@ module('Acceptance | host-catalogs | hosts | list', function (hooks) { newHost: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { @@ -72,35 +64,41 @@ module('Acceptance | host-catalogs | hosts | list', function (hooks) { urls.host = `${urls.hosts}/${instances.host.id}`; urls.unknownHost = `${urls.hosts}/foo`; urls.newHost = `${urls.hosts}/new`; - authenticateSession({}); + await authenticateSession({}); }); + test('Users can navigate to hosts with proper authorization', async function (assert) { await visit(urls.hostCatalog); + assert.ok( instances.hostCatalog.authorized_collection_actions.hosts.includes( 'list', ), ); - assert.dom(MANAGE_DROPDOWN_SELECTOR); - assert.ok(find(`[href="${urls.hosts}"]`)); + + assert.dom(selectors.MANAGE_DROPDOWN_HOST_CATALOG).isVisible(); + assert.dom(commonSelectors.HREF(urls.hosts)).isVisible(); }); test('User cannot navigate to index without either list or create actions', async function (assert) { instances.hostCatalog.authorized_collection_actions.hosts = []; await visit(urls.hostCatalog); + assert.notOk( instances.hostCatalog.authorized_collection_actions.hosts.includes( 'list', ), ); - assert.notOk(find(`[href="${urls.hosts}"]`)); + assert.dom(commonSelectors.HREF(urls.hosts)).doesNotExist(); }); test('User can navigate to index with only create action', async function (assert) { instances.hostCatalog.authorized_collection_actions.hosts = ['create']; await visit(urls.hostCatalog); - assert.ok(find(`[href="${urls.hosts}"]`)); - await click(MANAGE_DROPDOWN_SELECTOR); - assert.dom(NEW_HOST_SELECTOR).hasAttribute('href', urls.newHost); + + assert.dom(commonSelectors.HREF(urls.hosts)).isVisible(); + await click(selectors.MANAGE_DROPDOWN_HOST_CATALOG); + await click(selectors.MANAGE_DROPDOWN_HOST_CATALOG_NEW_HOST); + assert.strictEqual(currentURL(), urls.newHost); }); }); diff --git a/ui/admin/tests/acceptance/host-catalogs/hosts/read-test.js b/ui/admin/tests/acceptance/host-catalogs/hosts/read-test.js index e9ef8de8e7..9351fcb8ed 100644 --- a/ui/admin/tests/acceptance/host-catalogs/hosts/read-test.js +++ b/ui/admin/tests/acceptance/host-catalogs/hosts/read-test.js @@ -5,7 +5,7 @@ import { module, test } from 'qunit'; import { visit, currentURL, click } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { authenticateSession } from 'ember-simple-auth/test-support'; @@ -36,7 +36,7 @@ module('Acceptance | host-catalogs | hosts | read', function (hooks) { unknownHost: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { @@ -64,7 +64,7 @@ module('Acceptance | host-catalogs | hosts | read', function (hooks) { urls.host = `${urls.hosts}/${instances.host.id}`; urls.unknownHost = `${urls.hosts}/foo`; - authenticateSession({ username: 'admin' }); + await authenticateSession({ username: 'admin' }); }); test('visiting hosts', async function (assert) { @@ -85,7 +85,7 @@ module('Acceptance | host-catalogs | hosts | read', function (hooks) { await click(commonSelectors.HREF(urls.hosts)); - assert.dom(commonSelectors.TABLE_FIRST_ROW_RESOURCE_LINK).doesNotExist(); + assert.dom(commonSelectors.TABLE_RESOURCE_LINK(urls.host)).doesNotExist(); }); test('visiting an unknown host displays 404 message', async function (assert) { diff --git a/ui/admin/tests/acceptance/host-catalogs/hosts/selectors.js b/ui/admin/tests/acceptance/host-catalogs/hosts/selectors.js index 1a144c948e..d27a7e0bad 100644 --- a/ui/admin/tests/acceptance/host-catalogs/hosts/selectors.js +++ b/ui/admin/tests/acceptance/host-catalogs/hosts/selectors.js @@ -3,8 +3,14 @@ * SPDX-License-Identifier: BUSL-1.1 */ -export const MANAGE_DROPDOWN = +export const MANAGE_DROPDOWN_HOST_CATALOG = '[data-test-manage-host-catalogs-dropdown] button:first-child'; - +export const MANAGE_DROPDOWN_HOST_CATALOG_NEW_HOST = + '[data-test-manage-host-catalogs-dropdown] ul li:first-child a'; +export const MANAGE_DROPDOWN_HOST = + '[data-test-manage-hosts-dropdown] button:first-child'; +export const MANAGE_DROPDOWN_HOST_DELETE = + '[data-test-manage-hosts-dropdown] ul li button'; export const FIELD_ADDRESS = '[name=address]'; export const FIELD_ADDRESS_VALUE = 'New address'; +export const FIELD_ADDRESS_ERROR = '[data-test-error-message-address]'; diff --git a/ui/admin/tests/acceptance/host-catalogs/hosts/update-test.js b/ui/admin/tests/acceptance/host-catalogs/hosts/update-test.js index 3973c5fcdb..3965bcd30d 100644 --- a/ui/admin/tests/acceptance/host-catalogs/hosts/update-test.js +++ b/ui/admin/tests/acceptance/host-catalogs/hosts/update-test.js @@ -4,16 +4,13 @@ */ import { module, test } from 'qunit'; -import { visit, currentURL, find, click, fillIn } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { visit, currentURL, click, fillIn } from '@ember/test-helpers'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { Response } from 'miragejs'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; +import * as selectors from './selectors'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; module('Acceptance | host-catalogs | hosts | update', function (hooks) { setupApplicationTest(hooks); @@ -40,7 +37,7 @@ module('Acceptance | host-catalogs | hosts | update', function (hooks) { newHost: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { @@ -69,36 +66,51 @@ module('Acceptance | host-catalogs | hosts | update', function (hooks) { urls.unknownHost = `${urls.hosts}/foo`; urls.newHost = `${urls.hosts}/new`; // Generate resource couner - authenticateSession({}); + await authenticateSession({}); }); test('can save changes to existing host', async function (assert) { - assert.notEqual(instances.host.name, 'random string'); + assert.notEqual(instances.host.name, commonSelectors.FIELD_NAME_VALUE); await visit(urls.host); - await click('form [type="button"]', 'Activate edit mode'); - await fillIn('[name="name"]', 'random string'); - await click('.rose-form-actions [type="submit"]'); - assert.strictEqual(currentURL(), urls.host); - assert.strictEqual( - this.server.schema.hosts.all().models[0].name, - 'random string', + + await click(commonSelectors.EDIT_BTN, 'Activate edit mode'); + await fillIn(commonSelectors.FIELD_NAME, commonSelectors.FIELD_NAME_VALUE); + await fillIn( + commonSelectors.FIELD_DESCRIPTION, + commonSelectors.FIELD_DESCRIPTION_VALUE, ); + await click(commonSelectors.SAVE_BTN); + + const { name, description } = this.server.schema.hosts.all().models[0]; + assert.strictEqual(currentURL(), urls.host); + assert.strictEqual(name, commonSelectors.FIELD_NAME_VALUE); + assert.strictEqual(description, commonSelectors.FIELD_DESCRIPTION_VALUE); }); test('cannot make changes to an existing host without proper authorization', async function (assert) { instances.host.authorized_actions = instances.host.authorized_actions.filter((item) => item !== 'update'); await visit(urls.host); - assert.notOk(find('.rose-layout-page-actions .rose-button-secondary')); + + assert.dom(commonSelectors.EDIT_BTN).doesNotExist(); }); test('can cancel changes to existing host', async function (assert) { + const { name, description } = instances.host; await visit(urls.host); - await click('form [type="button"]', 'Activate edit mode'); - await fillIn('[name="name"]', 'random string'); - await click('.rose-form-actions [type="button"]'); - assert.notEqual(instances.host.name, 'random string'); - assert.strictEqual(find('[name="name"]').value, instances.host.name); + + await click(commonSelectors.EDIT_BTN, 'Activate edit mode'); + await fillIn(commonSelectors.FIELD_NAME, commonSelectors.FIELD_NAME_VALUE); + await fillIn( + commonSelectors.FIELD_DESCRIPTION, + commonSelectors.FIELD_DESCRIPTION_VALUE, + ); + await click(commonSelectors.CANCEL_BTN); + + assert.notEqual(name, commonSelectors.FIELD_NAME_VALUE); + assert.notEqual(description, commonSelectors.FIELD_DESCRIPTION_VALUE); + assert.dom(commonSelectors.FIELD_NAME).hasValue(name); + assert.dom(commonSelectors.FIELD_DESCRIPTION).hasValue(description); }); test('saving an existing host with invalid fields displays error messages', async function (assert) { @@ -113,8 +125,8 @@ module('Acceptance | host-catalogs | hosts | update', function (hooks) { details: { request_fields: [ { - name: 'name', - description: 'Name is required.', + name: 'address', + description: 'Address is required.', }, ], }, @@ -122,59 +134,73 @@ module('Acceptance | host-catalogs | hosts | update', function (hooks) { ); }); await visit(urls.host); - await click('form [type="button"]', 'Activate edit mode'); - await fillIn('[name="name"]', 'random string'); - await click('[type="submit"]'); + + await click(commonSelectors.EDIT_BTN, 'Activate edit mode'); + await fillIn(selectors.FIELD_ADDRESS, ''); + await click(commonSelectors.SAVE_BTN); + assert - .dom('[data-test-toast-notification] .hds-alert__description') + .dom(commonSelectors.ALERT_TOAST_BODY) .hasText('The request was invalid.'); - assert.ok( - find('[data-test-error-message-name]').textContent.trim(), - 'Name is required.', - ); + assert.dom(selectors.FIELD_ADDRESS_ERROR).hasText('Address is required.'); }); test('can discard unsaved host changes via dialog', async function (assert) { - assert.expect(5); + assert.expect(7); + const { name, description } = this.server.schema.hosts.all().models[0]; const confirmService = this.owner.lookup('service:confirm'); confirmService.enabled = true; - assert.notEqual(instances.host.name, 'random string'); + assert.notEqual(name, commonSelectors.FIELD_NAME_VALUE); + assert.notEqual(description, commonSelectors.FIELD_DESCRIPTION_VALUE); await visit(urls.host); - await click('form [type="button"]', 'Activate edit mode'); - await fillIn('[name="name"]', 'random string'); + + await click(commonSelectors.EDIT_BTN, 'Activate edit mode'); + await fillIn(commonSelectors.FIELD_NAME, commonSelectors.FIELD_NAME_VALUE); + await fillIn( + commonSelectors.FIELD_DESCRIPTION, + commonSelectors.FIELD_DESCRIPTION_VALUE, + ); assert.strictEqual(currentURL(), urls.host); + + // Wrap on a try/catch because transitioning while editing returns error try { await visit(urls.hosts); } catch (e) { - assert.ok(find('.rose-dialog')); - await click('.rose-dialog-footer button:first-child'); + assert.dom(commonSelectors.MODAL_WARNING).exists(); + await click(commonSelectors.MODAL_WARNING_CONFIRM_BTN); assert.strictEqual(currentURL(), urls.hosts); - assert.notEqual( - this.server.schema.hosts.all().models[0].name, - 'random string', - ); + assert.notEqual(name, commonSelectors.FIELD_NAME_VALUE); + assert.notEqual(description, commonSelectors.FIELD_DESCRIPTION_VALUE); } }); test('can cancel discard unsaved host changes via dialog', async function (assert) { - assert.expect(5); + assert.expect(7); + const { name, description } = this.server.schema.hosts.all().models[0]; const confirmService = this.owner.lookup('service:confirm'); confirmService.enabled = true; - assert.notEqual(instances.host.name, 'random string'); + assert.notEqual(name, commonSelectors.FIELD_NAME_VALUE); + assert.notEqual(description, commonSelectors.FIELD_DESCRIPTION_VALUE); await visit(urls.host); - await click('form [type="button"]', 'Activate edit mode'); - await fillIn('[name="name"]', 'random string'); + + await click(commonSelectors.EDIT_BTN, 'Activate edit mode'); + await fillIn(commonSelectors.FIELD_NAME, commonSelectors.FIELD_NAME_VALUE); + await fillIn( + commonSelectors.FIELD_DESCRIPTION, + commonSelectors.FIELD_DESCRIPTION_VALUE, + ); assert.strictEqual(currentURL(), urls.host); + + // Wrap on a try/catch because transitioning while editing returns error try { await visit(urls.hosts); } catch (e) { - assert.ok(find('.rose-dialog')); - await click('.rose-dialog-footer button:last-child'); + assert.dom(commonSelectors.MODAL_WARNING).exists(); + await click(commonSelectors.MODAL_WARNING_CANCEL_BTN); + assert.strictEqual(currentURL(), urls.host); - assert.notEqual( - this.server.schema.hosts.all().models[0].name, - 'random string', - ); + assert.notEqual(name, commonSelectors.FIELD_NAME_VALUE); + assert.notEqual(description, commonSelectors.FIELD_DESCRIPTION_VALUE); } }); }); diff --git a/ui/admin/tests/acceptance/host-catalogs/list-test.js b/ui/admin/tests/acceptance/host-catalogs/list-test.js index 76ba216e13..ff61bcabf9 100644 --- a/ui/admin/tests/acceptance/host-catalogs/list-test.js +++ b/ui/admin/tests/acceptance/host-catalogs/list-test.js @@ -5,21 +5,18 @@ import { module, test } from 'qunit'; import { visit, click, fillIn, waitFor } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import { TYPE_HOST_CATALOG_DYNAMIC, TYPE_HOST_CATALOG_STATIC, TYPE_HOST_CATALOG_PLUGIN_AWS, TYPE_HOST_CATALOG_PLUGIN_AZURE, + TYPE_HOST_CATALOG_PLUGIN_GCP, } from 'api/models/host-catalog'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; module('Acceptance | host-catalogs | list', function (hooks) { setupApplicationTest(hooks); @@ -33,6 +30,7 @@ module('Acceptance | host-catalogs | list', function (hooks) { project: null, }, staticHostCatalog: null, + gcpHostCatalog: null, }; const urls = { @@ -40,13 +38,16 @@ module('Acceptance | host-catalogs | list', function (hooks) { orgScope: null, projectScope: null, hostCatalogs: null, + awsHostCatalog: null, + azureHostCatalog: null, staticHostCatalog: null, + gcpHostCatalog: null, }; const SEARCH_INPUT_SELECTOR = '.search-filtering [type="search"]'; const NO_RESULTS_MSG_SELECTOR = '[data-test-no-host-catalog-results]'; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', @@ -70,6 +71,12 @@ module('Acceptance | host-catalogs | list', function (hooks) { type: TYPE_HOST_CATALOG_DYNAMIC, plugin: { name: TYPE_HOST_CATALOG_PLUGIN_AZURE }, }); + instances.gcpHostCatalog = this.server.create('host-catalog', { + scope: instances.scopes.project, + type: TYPE_HOST_CATALOG_DYNAMIC, + plugin: { name: TYPE_HOST_CATALOG_PLUGIN_GCP }, + }); + urls.globalScope = `/scopes/global/scopes`; urls.orgScope = `/scopes/${instances.scopes.org.id}/scopes`; urls.projectScope = `/scopes/${instances.scopes.project.id}`; @@ -77,8 +84,9 @@ module('Acceptance | host-catalogs | list', function (hooks) { urls.staticHostCatalog = `${urls.hostCatalogs}/${instances.staticHostCatalog.id}`; urls.awsHostCatalog = `${urls.hostCatalogs}/${instances.awsHostCatalog.id}`; urls.azureHostCatalog = `${urls.hostCatalogs}/${instances.azureHostCatalog.id}`; + urls.gcpHostCatalog = `${urls.hostCatalogs}/${instances.gcpHostCatalog.id}`; - authenticateSession({}); + await authenticateSession({}); }); test('user can navigate to host catalogs with proper authorization', async function (assert) { @@ -143,7 +151,10 @@ module('Acceptance | host-catalogs | list', function (hooks) { await click(`[href="${urls.hostCatalogs}"]`); - assert.dom('.rose-table-body tr:first-child a').doesNotExist(); + assert.dom('.hds-application-state__body-text').isVisible(); + assert + .dom(commonSelectors.TABLE_RESOURCE_LINK(urls.staticHostCatalog)) + .doesNotExist(); }); test('user can navigate to index with only list action', async function (assert) { @@ -214,6 +225,21 @@ module('Acceptance | host-catalogs | list', function (hooks) { assert.dom(`[href="${urls.azureHostCatalog}"]`).exists(); }); + test('user can search for gcp host catalog', async function (assert) { + await visit(urls.projectScope); + + await click(`[href="${urls.hostCatalogs}"]`); + assert.dom(`[href="${urls.staticHostCatalog}"]`).isVisible(); + assert.dom(`[href="${urls.awsHostCatalog}"]`).isVisible(); + assert.dom(`[href="${urls.azureHostCatalog}"]`).isVisible(); + assert.dom(`[href="${urls.gcpHostCatalog}"]`).isVisible(); + + await fillIn(SEARCH_INPUT_SELECTOR, TYPE_HOST_CATALOG_PLUGIN_GCP); + await waitFor(`[href="${urls.staticHostCatalog}"]`, { count: 0 }); + + assert.dom(`[href="${urls.gcpHostCatalog}"]`).isVisible(); + }); + test('user can search for host catalogs and get no results', async function (assert) { await visit(urls.projectScope); diff --git a/ui/admin/tests/acceptance/host-catalogs/read-test.js b/ui/admin/tests/acceptance/host-catalogs/read-test.js index b90cd4e702..790575beb1 100644 --- a/ui/admin/tests/acceptance/host-catalogs/read-test.js +++ b/ui/admin/tests/acceptance/host-catalogs/read-test.js @@ -5,16 +5,12 @@ import { module, test } from 'qunit'; import { visit, currentURL, click } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; module('Acceptance | host-catalogs | read', function (hooks) { setupApplicationTest(hooks); @@ -38,7 +34,7 @@ module('Acceptance | host-catalogs | read', function (hooks) { hostCatalog: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { @@ -60,7 +56,7 @@ module('Acceptance | host-catalogs | read', function (hooks) { urls.hostCatalog = `${urls.hostCatalogs}/${instances.hostCatalog.id}`; urls.unknownHostCatalog = `${urls.hostCatalogs}/foo`; - authenticateSession({ username: 'admin' }); + await authenticateSession({ username: 'admin' }); }); test('visiting host catalogs', async function (assert) { @@ -83,14 +79,18 @@ module('Acceptance | host-catalogs | read', function (hooks) { await click(`[href="${urls.hostCatalogs}"]`); - assert.dom('.rose-table-body tr:first-child a').doesNotExist(); + assert + .dom(commonSelectors.TABLE_RESOURCE_LINK(urls.hostCatalog)) + .doesNotExist(); }); test('visiting an unknown host catalog displays 404 message', async function (assert) { await visit(urls.unknownHostCatalog); await a11yAudit(); - assert.dom('.rose-message-subtitle').hasText('Error 404'); + assert + .dom(commonSelectors.RESOURCE_NOT_FOUND_SUBTITLE) + .hasText(commonSelectors.RESOURCE_NOT_FOUND_VALUE); }); test('users can link to docs page for host catalog', async function (assert) { diff --git a/ui/admin/tests/acceptance/host-catalogs/update-test.js b/ui/admin/tests/acceptance/host-catalogs/update-test.js index ab7d216363..4ad8931030 100644 --- a/ui/admin/tests/acceptance/host-catalogs/update-test.js +++ b/ui/admin/tests/acceptance/host-catalogs/update-test.js @@ -5,19 +5,16 @@ import { module, test } from 'qunit'; import { visit, currentURL, click, fillIn, find } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { Response } from 'miragejs'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; import { TYPE_HOST_CATALOG_DYNAMIC, TYPE_HOST_CATALOG_PLUGIN_AWS, + TYPE_HOST_CATALOG_PLUGIN_GCP, } from 'api/models/host-catalog'; module('Acceptance | host-catalogs | update', function (hooks) { @@ -32,6 +29,7 @@ module('Acceptance | host-catalogs | update', function (hooks) { project: null, }, hostCatalog: null, + GCPHostCatalog: null, }; const urls = { globalScope: null, @@ -40,19 +38,17 @@ module('Acceptance | host-catalogs | update', function (hooks) { hostCatalogs: null, hostCatalog: null, AWSHostCatalogWithStaticCredential: null, + GCPHostCatalog: null, }; const NAME_INPUT_SELECTOR = '[name="name"]'; - const EDIT_BUTTON_SELECTOR = 'form [type="button"]'; + const EDIT_BUTTON_SELECTOR = '.rose-form-actions [type=button]'; const SAVE_BUTTON_SELECTOR = '.rose-form-actions [type="submit"]'; const CANCEL_BUTTON_SELECTOR = '.rose-form-actions [type="button"]'; - const MODAL_DISCARD_BUTTON_SELECTOR = - '.rose-dialog-footer button:first-child'; - const MODAL_CANCEL_BUTTON_SELECTOR = '.rose-dialog-footer button:last-child'; const CREDENTIAL_TYPE_SELECTOR = '.dynamic-credential-selection input:checked'; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { @@ -66,6 +62,12 @@ module('Acceptance | host-catalogs | update', function (hooks) { instances.hostCatalog = this.server.create('host-catalog', { scope: instances.scopes.project, }); + instances.GCPHostCatalog = this.server.create('host-catalog', { + scope: instances.scopes.project, + type: TYPE_HOST_CATALOG_DYNAMIC, + plugin: { name: TYPE_HOST_CATALOG_PLUGIN_GCP }, + }); + instances.AWSHostCatalogWithStaticCredential = this.server.create( 'host-catalog', { @@ -82,12 +84,14 @@ module('Acceptance | host-catalogs | update', function (hooks) { urls.hostCatalogs = `${urls.projectScope}/host-catalogs`; urls.hostCatalog = `${urls.hostCatalogs}/${instances.hostCatalog.id}`; urls.AWSHostCatalogWithStaticCredential = `${urls.hostCatalogs}/${instances.AWSHostCatalogWithStaticCredential.id}`; - authenticateSession({}); + urls.GCPHostCatalog = `${urls.hostCatalogs}/${instances.GCPHostCatalog.id}`; + await authenticateSession({}); }); test('can update static AWS credentials to Dynamic AWS credentials', async function (assert) { await visit(urls.AWSHostCatalogWithStaticCredential); await click(EDIT_BUTTON_SELECTOR, 'Activate edit mode'); + assert.strictEqual( find(CREDENTIAL_TYPE_SELECTOR).value, 'static-credential', @@ -103,6 +107,19 @@ module('Acceptance | host-catalogs | update', function (hooks) { ); }); + test('can update GCP host catalog', async function (assert) { + await visit(urls.GCPHostCatalog); + await click(EDIT_BUTTON_SELECTOR, 'Activate edit mode'); + await fillIn('[name=project_id]', 'project-id'); + await click(SAVE_BUTTON_SELECTOR); + + assert.strictEqual( + this.server.schema.hostCatalogs.where({ type: 'plugin' }).models[0] + .attributes.project_id, + 'project-id', + ); + }); + test('can save changes to existing host catalog', async function (assert) { assert.notEqual(instances.hostCatalog.name, 'random string'); await visit(urls.hostCatalog); @@ -183,8 +200,8 @@ module('Acceptance | host-catalogs | update', function (hooks) { await fillIn(NAME_INPUT_SELECTOR, 'random string'); assert.strictEqual(currentURL(), urls.hostCatalog); await click(`[href="${urls.hostCatalogs}"]`); - assert.dom('.rose-dialog').exists(); - await click(MODAL_DISCARD_BUTTON_SELECTOR, 'Click Discard'); + assert.dom(commonSelectors.MODAL_WARNING).exists(); + await click(commonSelectors.MODAL_WARNING_CONFIRM_BTN, 'Click Discard'); assert.strictEqual(currentURL(), urls.hostCatalogs); assert.notEqual( @@ -203,8 +220,8 @@ module('Acceptance | host-catalogs | update', function (hooks) { await fillIn(NAME_INPUT_SELECTOR, 'random string'); assert.strictEqual(currentURL(), urls.hostCatalog); await click(`[href="${urls.hostCatalogs}"]`); - assert.dom('.rose-dialog').exists(); - await click(MODAL_CANCEL_BUTTON_SELECTOR, 'Click Cancel'); + assert.dom(commonSelectors.MODAL_WARNING).exists(); + await click(commonSelectors.MODAL_WARNING_CANCEL_BTN, 'Click Cancel'); assert.strictEqual(currentURL(), urls.hostCatalog); assert.notEqual( diff --git a/ui/admin/tests/acceptance/managed-groups/create-test.js b/ui/admin/tests/acceptance/managed-groups/create-test.js index 83f13feea3..c82c5fb374 100644 --- a/ui/admin/tests/acceptance/managed-groups/create-test.js +++ b/ui/admin/tests/acceptance/managed-groups/create-test.js @@ -5,7 +5,7 @@ import { module, test } from 'qunit'; import { visit, currentURL, click, fillIn } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import { authenticateSession } from 'ember-simple-auth/test-support'; @@ -53,8 +53,8 @@ module('Acceptance | managed-groups | create', function (hooks) { newLdapManagedGroup: null, }; - hooks.beforeEach(function () { - authenticateSession({ username: 'admin' }); + hooks.beforeEach(async function () { + await authenticateSession({ username: 'admin' }); instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', diff --git a/ui/admin/tests/acceptance/managed-groups/delete-test.js b/ui/admin/tests/acceptance/managed-groups/delete-test.js index 32d08b27f4..343e656bda 100644 --- a/ui/admin/tests/acceptance/managed-groups/delete-test.js +++ b/ui/admin/tests/acceptance/managed-groups/delete-test.js @@ -5,7 +5,7 @@ import { module, test } from 'qunit'; import { visit, click, currentURL } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; 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'; @@ -50,8 +50,8 @@ module('Acceptance | managed-groups | delete', function (hooks) { ldapManagedGroup: null, }; - hooks.beforeEach(function () { - authenticateSession({ username: 'admin' }); + hooks.beforeEach(async function () { + await authenticateSession({ username: 'admin' }); instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', diff --git a/ui/admin/tests/acceptance/managed-groups/list-test.js b/ui/admin/tests/acceptance/managed-groups/list-test.js index eda54202f2..f906a9af74 100644 --- a/ui/admin/tests/acceptance/managed-groups/list-test.js +++ b/ui/admin/tests/acceptance/managed-groups/list-test.js @@ -5,7 +5,7 @@ import { module, test } from 'qunit'; import { visit, click } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import { authenticateSession } from 'ember-simple-auth/test-support'; @@ -43,8 +43,8 @@ module('Acceptance | managed-groups | list', function (hooks) { newLdapManagedGroup: null, }; - hooks.beforeEach(function () { - authenticateSession({}); + hooks.beforeEach(async function () { + await authenticateSession({}); instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', diff --git a/ui/admin/tests/acceptance/managed-groups/members-test.js b/ui/admin/tests/acceptance/managed-groups/members-test.js index 959706a62b..95f1ec123e 100644 --- a/ui/admin/tests/acceptance/managed-groups/members-test.js +++ b/ui/admin/tests/acceptance/managed-groups/members-test.js @@ -5,15 +5,10 @@ import { module, test } from 'qunit'; import { visit, currentURL, findAll } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; import { TYPE_AUTH_METHOD_OIDC } from 'api/models/auth-method'; module('Acceptance | managed-groups | members', function (hooks) { @@ -38,8 +33,8 @@ module('Acceptance | managed-groups | members', function (hooks) { managedGroupMembers: null, }; - hooks.beforeEach(function () { - authenticateSession({ username: 'admin' }); + hooks.beforeEach(async function () { + await authenticateSession({ username: 'admin' }); instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', diff --git a/ui/admin/tests/acceptance/managed-groups/read-test.js b/ui/admin/tests/acceptance/managed-groups/read-test.js index d4dac17a26..ddeddf5311 100644 --- a/ui/admin/tests/acceptance/managed-groups/read-test.js +++ b/ui/admin/tests/acceptance/managed-groups/read-test.js @@ -5,7 +5,7 @@ import { module, test } from 'qunit'; import { visit, click, currentURL } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; 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'; @@ -39,8 +39,8 @@ module('Acceptance | managed-groups | read', function (hooks) { ldapManagedGroup: null, }; - hooks.beforeEach(function () { - authenticateSession({ username: 'admin' }); + hooks.beforeEach(async function () { + await authenticateSession({ username: 'admin' }); instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', diff --git a/ui/admin/tests/acceptance/onboarding/onboarding-test.js b/ui/admin/tests/acceptance/onboarding/onboarding-test.js index 8e46b1a9e8..3d5da16362 100644 --- a/ui/admin/tests/acceptance/onboarding/onboarding-test.js +++ b/ui/admin/tests/acceptance/onboarding/onboarding-test.js @@ -4,7 +4,7 @@ */ import { module, test } from 'qunit'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { authenticateSession } from 'ember-simple-auth/test-support'; import { visit, fillIn, click, currentURL } from '@ember/test-helpers'; @@ -19,8 +19,8 @@ module('Acceptance | onboarding', function (hooks) { orgs: '/scopes/global/scopes', }; - hooks.beforeEach(() => { - authenticateSession({}); + hooks.beforeEach(async () => { + await authenticateSession({}); }); test('show targetAddress and targetPort fields', async function (assert) { diff --git a/ui/admin/tests/acceptance/onboarding/success-test.js b/ui/admin/tests/acceptance/onboarding/success-test.js index 680dd6add0..2926935a1d 100644 --- a/ui/admin/tests/acceptance/onboarding/success-test.js +++ b/ui/admin/tests/acceptance/onboarding/success-test.js @@ -5,7 +5,7 @@ import { module, test } from 'qunit'; import { visit, currentURL, find, click, fillIn } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { authenticateSession } from 'ember-simple-auth/test-support'; @@ -13,8 +13,8 @@ module('Acceptance | onboarding | success', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); - hooks.beforeEach(function () { - authenticateSession({}); + hooks.beforeEach(async function () { + await authenticateSession({}); }); const urls = { diff --git a/ui/admin/tests/acceptance/policy/create-test.js b/ui/admin/tests/acceptance/policy/create-test.js index e0658c4dca..ed7853477c 100644 --- a/ui/admin/tests/acceptance/policy/create-test.js +++ b/ui/admin/tests/acceptance/policy/create-test.js @@ -45,7 +45,7 @@ module('Acceptance | policies | create', function (hooks) { newPolicy: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', @@ -57,7 +57,7 @@ module('Acceptance | policies | create', function (hooks) { getPolicyCount = () => this.server.schema.policies.all().models.length; features = this.owner.lookup('service:features'); features.enable('ssh-session-recording'); - authenticateSession({ username: 'admin' }); + await authenticateSession({ username: 'admin' }); }); test('users can create a new policy with global scope', async function (assert) { diff --git a/ui/admin/tests/acceptance/policy/delete-test.js b/ui/admin/tests/acceptance/policy/delete-test.js index 1c8232c3a8..4ad1162477 100644 --- a/ui/admin/tests/acceptance/policy/delete-test.js +++ b/ui/admin/tests/acceptance/policy/delete-test.js @@ -35,8 +35,8 @@ module('Acceptance | policies | delete', function (hooks) { policy: null, }; - hooks.beforeEach(function () { - authenticateSession({}); + hooks.beforeEach(async function () { + await authenticateSession({}); instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', diff --git a/ui/admin/tests/acceptance/policy/list-test.js b/ui/admin/tests/acceptance/policy/list-test.js index 20657ba209..cb7faa2e49 100644 --- a/ui/admin/tests/acceptance/policy/list-test.js +++ b/ui/admin/tests/acceptance/policy/list-test.js @@ -9,6 +9,7 @@ import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import { authenticateSession } from 'ember-simple-auth/test-support'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; module('Acceptance | policies | list', function (hooks) { setupApplicationTest(hooks); @@ -19,8 +20,6 @@ module('Acceptance | policies | list', function (hooks) { let intl; const STORAGE_POLICY_TITLE = 'Storage Policies'; - const MESSAGE_DESCRIPTION_SELECTOR = '.rose-message-description'; - const MESSAGE_LINK_SELECTOR = '.rose-message-body .hds-link-standalone'; const DROPDOWN_BUTTON_SELECTOR = '.hds-dropdown-toggle-icon'; const DROPDOWN_ITEM_SELECTOR = '.hds-dropdown-list-item a'; @@ -38,7 +37,7 @@ module('Acceptance | policies | list', function (hooks) { policy: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', @@ -49,7 +48,7 @@ module('Acceptance | policies | list', function (hooks) { intl = this.owner.lookup('service:intl'); - authenticateSession({}); + await authenticateSession({}); featuresService = this.owner.lookup('service:features'); }); @@ -72,9 +71,9 @@ module('Acceptance | policies | list', function (hooks) { await click(`[href="${urls.policies}"]`); assert - .dom(MESSAGE_DESCRIPTION_SELECTOR) + .dom(commonSelectors.PAGE_MESSAGE_DESCRIPTION) .hasText(intl.t('resources.policy.messages.none.description')); - assert.dom(MESSAGE_LINK_SELECTOR).exists(); + assert.dom(commonSelectors.PAGE_MESSAGE_LINK).exists(); }); test('user cannot navigate to index without either list or create actions', async function (assert) { @@ -100,12 +99,12 @@ module('Acceptance | policies | list', function (hooks) { await visit(urls.policies); - assert.dom(MESSAGE_DESCRIPTION_SELECTOR).hasText( + assert.dom(commonSelectors.PAGE_MESSAGE_DESCRIPTION).hasText( intl.t('descriptions.neither-list-nor-create', { resource: STORAGE_POLICY_TITLE, }), ); - assert.dom(MESSAGE_LINK_SELECTOR).doesNotExist(); + assert.dom(commonSelectors.PAGE_MESSAGE_LINK).doesNotExist(); }); test('user can navigate to index with only create action', async function (assert) { @@ -132,12 +131,12 @@ module('Acceptance | policies | list', function (hooks) { await click(`[href="${urls.policies}"]`); - assert.dom(MESSAGE_DESCRIPTION_SELECTOR).hasText( + assert.dom(commonSelectors.PAGE_MESSAGE_DESCRIPTION).hasText( intl.t('descriptions.create-but-not-list', { resource: STORAGE_POLICY_TITLE, }), ); - assert.dom(MESSAGE_LINK_SELECTOR).exists(); + assert.dom(commonSelectors.PAGE_MESSAGE_LINK).exists(); }); test('user can navigate to index with only list action', async function (assert) { @@ -165,9 +164,9 @@ module('Acceptance | policies | list', function (hooks) { await click(`[href="${urls.policies}"]`); assert - .dom(MESSAGE_DESCRIPTION_SELECTOR) + .dom(commonSelectors.PAGE_MESSAGE_DESCRIPTION) .hasText(intl.t('resources.policy.messages.none.description')); - assert.dom(MESSAGE_LINK_SELECTOR).doesNotExist(); + assert.dom(commonSelectors.PAGE_MESSAGE_LINK).doesNotExist(); }); test('user cannot navigate to index when feature is disabled', async function (assert) { diff --git a/ui/admin/tests/acceptance/policy/read-test.js b/ui/admin/tests/acceptance/policy/read-test.js index f012212c53..5982811999 100644 --- a/ui/admin/tests/acceptance/policy/read-test.js +++ b/ui/admin/tests/acceptance/policy/read-test.js @@ -10,6 +10,7 @@ import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { authenticateSession } from 'ember-simple-auth/test-support'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; module('Acceptance | policies | read', function (hooks) { setupApplicationTest(hooks); @@ -18,7 +19,6 @@ module('Acceptance | policies | read', function (hooks) { let features; - const MESSAGE_SELECTOR = '.rose-message-subtitle'; const TABLE_LINK_SELECTOR = '.hds-table__tbody tr:first-child a'; const instances = { @@ -36,7 +36,7 @@ module('Acceptance | policies | read', function (hooks) { unknownPolicy: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', @@ -52,7 +52,7 @@ module('Acceptance | policies | read', function (hooks) { features = this.owner.lookup('service:features'); features.enable('ssh-session-recording'); - authenticateSession({ username: 'admin' }); + await authenticateSession({ username: 'admin' }); }); test('visiting a policy', async function (assert) { @@ -81,6 +81,8 @@ module('Acceptance | policies | read', function (hooks) { await visit(urls.unknownPolicy); await a11yAudit(); - assert.dom(MESSAGE_SELECTOR).hasText('Error 404'); + assert + .dom(commonSelectors.RESOURCE_NOT_FOUND_SUBTITLE) + .hasText(commonSelectors.RESOURCE_NOT_FOUND_VALUE); }); }); diff --git a/ui/admin/tests/acceptance/policy/update-test.js b/ui/admin/tests/acceptance/policy/update-test.js index b5de7d8654..398fdec030 100644 --- a/ui/admin/tests/acceptance/policy/update-test.js +++ b/ui/admin/tests/acceptance/policy/update-test.js @@ -43,7 +43,7 @@ module('Acceptance | policies | update', function (hooks) { policy: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.policy = this.server.create('policy', { @@ -56,7 +56,7 @@ module('Acceptance | policies | update', function (hooks) { features = this.owner.lookup('service:features'); features.enable('ssh-session-recording'); - authenticateSession({}); + await authenticateSession({}); }); test('users can update forever select option to a custom input', async function (assert) { diff --git a/ui/admin/tests/acceptance/roles/create-test.js b/ui/admin/tests/acceptance/roles/create-test.js index 59cf73da0b..39e509c964 100644 --- a/ui/admin/tests/acceptance/roles/create-test.js +++ b/ui/admin/tests/acceptance/roles/create-test.js @@ -5,17 +5,12 @@ import { module, test } from 'qunit'; import { visit, currentURL, click, fillIn, find } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { Response } from 'miragejs'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; module('Acceptance | roles | create', function (hooks) { setupApplicationTest(hooks); @@ -37,8 +32,8 @@ module('Acceptance | roles | create', function (hooks) { orgScope: null, }; - hooks.beforeEach(function () { - authenticateSession({ username: 'admin' }); + hooks.beforeEach(async function () { + await authenticateSession({ username: 'admin' }); instances.scopes.org = this.server.create( 'scope', { diff --git a/ui/admin/tests/acceptance/roles/delete-test.js b/ui/admin/tests/acceptance/roles/delete-test.js index 3a13805869..c43e21c43f 100644 --- a/ui/admin/tests/acceptance/roles/delete-test.js +++ b/ui/admin/tests/acceptance/roles/delete-test.js @@ -5,15 +5,10 @@ import { module, test } from 'qunit'; import { visit, click, find } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { Response } from 'miragejs'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; module('Acceptance | roles | delete', function (hooks) { setupApplicationTest(hooks); @@ -38,8 +33,8 @@ module('Acceptance | roles | delete', function (hooks) { newRole: null, }; - hooks.beforeEach(function () { - authenticateSession({}); + hooks.beforeEach(async function () { + await authenticateSession({}); instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', diff --git a/ui/admin/tests/acceptance/roles/global-scope-test.js b/ui/admin/tests/acceptance/roles/global-scope-test.js index c9718b461c..cf83dd6c0f 100644 --- a/ui/admin/tests/acceptance/roles/global-scope-test.js +++ b/ui/admin/tests/acceptance/roles/global-scope-test.js @@ -12,11 +12,12 @@ import { fillIn, waitUntil, } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { authenticateSession } from 'ember-simple-auth/test-support'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; import { GRANT_SCOPE_THIS, GRANT_SCOPE_CHILDREN, @@ -58,11 +59,6 @@ module('Acceptance | roles | global-scope', function (hooks) { const BUTTON_ICON_SELECTOR = '.hds-button__icon [data-test-icon="check-circle"]'; const PAGINATION_SELECTOR = '.hds-pagination'; - const DISCARD_CHANGES_DIALOG = '.rose-dialog'; - const DISCARD_CHANGES_DISCARD_BUTTON = - '.rose-dialog-footer .rose-button-primary'; - const DISCARD_CHANGES_CANCEL_BUTTON = - '.rose-dialog-footer .rose-button-secondary'; const REMOVE_ORG_MODAL = (name) => `[data-test-manage-scopes-remove-${name}-modal]`; const REMOVE_ORG_PROJECTS_BUTTON = '.hds-modal .hds-button--color-primary'; @@ -86,8 +82,8 @@ module('Acceptance | roles | global-scope', function (hooks) { manageCustomScopes: null, }; - hooks.beforeEach(function () { - authenticateSession({ username: 'admin' }); + hooks.beforeEach(async function () { + await authenticateSession({ username: 'admin' }); confirmService = this.owner.lookup('service:confirm'); instances.scopes.global = this.server.create('scope', { id: 'global' }); @@ -376,9 +372,9 @@ module('Acceptance | roles | global-scope', function (hooks) { await click(SCOPE_TOGGLE_SELECTOR(GRANT_SCOPE_THIS)); await click(`[href="${urls.roles}"]`); - assert.dom(DISCARD_CHANGES_DIALOG).isVisible(); + assert.dom(commonSelectors.MODAL_WARNING).isVisible(); - await click(DISCARD_CHANGES_DISCARD_BUTTON); + await click(commonSelectors.MODAL_WARNING_CONFIRM_BTN); assert.strictEqual(currentURL(), urls.roles); }); @@ -393,9 +389,9 @@ module('Acceptance | roles | global-scope', function (hooks) { await click(SCOPE_TOGGLE_SELECTOR(GRANT_SCOPE_THIS)); await click(`[href="${urls.roles}"]`); - assert.dom(DISCARD_CHANGES_DIALOG).isVisible(); + assert.dom(commonSelectors.MODAL_WARNING).isVisible(); - await click(DISCARD_CHANGES_CANCEL_BUTTON); + await click(commonSelectors.MODAL_WARNING_CANCEL_BTN); assert.strictEqual(currentURL(), urls.manageScopes); }); @@ -503,9 +499,9 @@ module('Acceptance | roles | global-scope', function (hooks) { await click(`[href="${urls.roles}"]`); - assert.dom(DISCARD_CHANGES_DIALOG).isVisible(); + assert.dom(commonSelectors.MODAL_WARNING).isVisible(); - await click(DISCARD_CHANGES_DISCARD_BUTTON); + await click(commonSelectors.MODAL_WARNING_CONFIRM_BTN); assert.strictEqual(currentURL(), urls.roles); }); @@ -520,9 +516,9 @@ module('Acceptance | roles | global-scope', function (hooks) { await click(`[href="${urls.roles}"]`); - assert.dom(DISCARD_CHANGES_DIALOG).isVisible(); + assert.dom(commonSelectors.MODAL_WARNING).isVisible(); - await click(DISCARD_CHANGES_CANCEL_BUTTON); + await click(commonSelectors.MODAL_WARNING_CANCEL_BTN); assert.strictEqual(currentURL(), urls.manageCustomScopes); }); @@ -841,9 +837,9 @@ module('Acceptance | roles | global-scope', function (hooks) { ); await click(`[href="${urls.roles}"]`); - assert.dom(DISCARD_CHANGES_DIALOG).isVisible(); + assert.dom(commonSelectors.MODAL_WARNING).isVisible(); - await click(DISCARD_CHANGES_DISCARD_BUTTON); + await click(commonSelectors.MODAL_WARNING_CONFIRM_BTN); assert.strictEqual(currentURL(), urls.roles); }); @@ -862,9 +858,9 @@ module('Acceptance | roles | global-scope', function (hooks) { ); await click(`[href="${urls.roles}"]`); - assert.dom(DISCARD_CHANGES_DIALOG).isVisible(); + assert.dom(commonSelectors.MODAL_WARNING).isVisible(); - await click(DISCARD_CHANGES_CANCEL_BUTTON); + await click(commonSelectors.MODAL_WARNING_CANCEL_BTN); assert.strictEqual( currentURL(), diff --git a/ui/admin/tests/acceptance/roles/grants-test.js b/ui/admin/tests/acceptance/roles/grants-test.js index 6f733799aa..1061d271cd 100644 --- a/ui/admin/tests/acceptance/roles/grants-test.js +++ b/ui/admin/tests/acceptance/roles/grants-test.js @@ -12,16 +12,11 @@ import { find, findAll, } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { Response } from 'miragejs'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; module('Acceptance | roles | grants', function (hooks) { setupApplicationTest(hooks); @@ -44,8 +39,8 @@ module('Acceptance | roles | grants', function (hooks) { const grantsForm = 'form:nth-child(2)'; let grantsCount; - hooks.beforeEach(function () { - authenticateSession({ username: 'admin' }); + hooks.beforeEach(async function () { + await authenticateSession({ username: 'admin' }); instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', diff --git a/ui/admin/tests/acceptance/roles/list-test.js b/ui/admin/tests/acceptance/roles/list-test.js index 8f60acb05d..131463aa98 100644 --- a/ui/admin/tests/acceptance/roles/list-test.js +++ b/ui/admin/tests/acceptance/roles/list-test.js @@ -12,15 +12,10 @@ import { waitUntil, focus, } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; import { GRANT_SCOPE_THIS } from 'api/models/role'; module('Acceptance | roles | list', function (hooks) { @@ -54,7 +49,7 @@ module('Acceptance | roles | list', function (hooks) { role2: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', @@ -71,7 +66,7 @@ module('Acceptance | roles | list', function (hooks) { urls.roles = `/scopes/${instances.scopes.org.id}/roles`; urls.role1 = `${urls.roles}/${instances.role1.id}`; urls.role2 = `${urls.roles}/${instances.role2.id}`; - authenticateSession({}); + await authenticateSession({}); }); test('users can navigate to roles with proper authorization', async function (assert) { diff --git a/ui/admin/tests/acceptance/roles/org-scope-test.js b/ui/admin/tests/acceptance/roles/org-scope-test.js index d3a278e5ab..2a6ce7c57d 100644 --- a/ui/admin/tests/acceptance/roles/org-scope-test.js +++ b/ui/admin/tests/acceptance/roles/org-scope-test.js @@ -12,11 +12,12 @@ import { fillIn, waitUntil, } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { authenticateSession } from 'ember-simple-auth/test-support'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; import { GRANT_SCOPE_THIS, GRANT_SCOPE_CHILDREN, @@ -55,12 +56,6 @@ module('Acceptance | roles | org-scope', function (hooks) { const BUTTON_ICON_SELECTOR = '.hds-button__icon [data-test-icon="check-circle"]'; const PAGINATION_SELECTOR = '.hds-pagination'; - const DISCARD_CHANGES_DIALOG = '.rose-dialog'; - const DISCARD_CHANGES_DISCARD_BUTTON = - '.rose-dialog-footer .rose-button-primary'; - const DISCARD_CHANGES_CANCEL_BUTTON = - '.rose-dialog-footer .rose-button-secondary'; - const instances = { scopes: { global: null, @@ -79,8 +74,8 @@ module('Acceptance | roles | org-scope', function (hooks) { manageOrgProjects: null, }; - hooks.beforeEach(function () { - authenticateSession({ username: 'admin' }); + hooks.beforeEach(async function () { + await authenticateSession({ username: 'admin' }); confirmService = this.owner.lookup('service:confirm'); instances.scopes.global = this.server.create('scope', { id: 'global' }); @@ -327,9 +322,9 @@ module('Acceptance | roles | org-scope', function (hooks) { await click(SCOPE_TOGGLE_SELECTOR(GRANT_SCOPE_THIS)); await click(`[href="${urls.roles}"]`); - assert.dom(DISCARD_CHANGES_DIALOG).isVisible(); + assert.dom(commonSelectors.MODAL_WARNING).isVisible(); - await click(DISCARD_CHANGES_DISCARD_BUTTON); + await click(commonSelectors.MODAL_WARNING_CONFIRM_BTN); assert.strictEqual(currentURL(), urls.roles); }); @@ -344,9 +339,9 @@ module('Acceptance | roles | org-scope', function (hooks) { await click(SCOPE_TOGGLE_SELECTOR(GRANT_SCOPE_THIS)); await click(`[href="${urls.roles}"]`); - assert.dom(DISCARD_CHANGES_DIALOG).isVisible(); + assert.dom(commonSelectors.MODAL_WARNING).isVisible(); - await click(DISCARD_CHANGES_CANCEL_BUTTON); + await click(commonSelectors.MODAL_WARNING_CANCEL_BTN); assert.strictEqual(currentURL(), urls.manageScopes); }); @@ -456,9 +451,9 @@ module('Acceptance | roles | org-scope', function (hooks) { ); await click(`[href="${urls.roles}"]`); - assert.dom(DISCARD_CHANGES_DIALOG).isVisible(); + assert.dom(commonSelectors.MODAL_WARNING).isVisible(); - await click(DISCARD_CHANGES_DISCARD_BUTTON); + await click(commonSelectors.MODAL_WARNING_CONFIRM_BTN); assert.strictEqual(currentURL(), urls.roles); }); @@ -474,9 +469,9 @@ module('Acceptance | roles | org-scope', function (hooks) { ); await click(`[href="${urls.roles}"]`); - assert.dom(DISCARD_CHANGES_DIALOG).isVisible(); + assert.dom(commonSelectors.MODAL_WARNING).isVisible(); - await click(DISCARD_CHANGES_CANCEL_BUTTON); + await click(commonSelectors.MODAL_WARNING_CANCEL_BTN); assert.strictEqual(currentURL(), urls.manageOrgProjects); }); diff --git a/ui/admin/tests/acceptance/roles/principals-test.js b/ui/admin/tests/acceptance/roles/principals-test.js index ab2b402c35..26b7755147 100644 --- a/ui/admin/tests/acceptance/roles/principals-test.js +++ b/ui/admin/tests/acceptance/roles/principals-test.js @@ -5,17 +5,12 @@ import { module, test } from 'qunit'; import { visit, currentURL, click, find, findAll } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { Response } from 'miragejs'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; module('Acceptance | roles | principals', function (hooks) { setupApplicationTest(hooks); @@ -40,8 +35,8 @@ module('Acceptance | roles | principals', function (hooks) { }; let principalsCount; - hooks.beforeEach(function () { - authenticateSession({ username: 'admin' }); + hooks.beforeEach(async function () { + await authenticateSession({ username: 'admin' }); instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', diff --git a/ui/admin/tests/acceptance/roles/project-scope-test.js b/ui/admin/tests/acceptance/roles/project-scope-test.js index 39c2cf1345..1bbb4699ea 100644 --- a/ui/admin/tests/acceptance/roles/project-scope-test.js +++ b/ui/admin/tests/acceptance/roles/project-scope-test.js @@ -5,7 +5,7 @@ import { module, test } from 'qunit'; import { visit, currentURL, click } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { authenticateSession } from 'ember-simple-auth/test-support'; @@ -40,8 +40,8 @@ module('Acceptance | roles | project-scope', function (hooks) { roleScopes: null, }; - hooks.beforeEach(function () { - authenticateSession({ username: 'admin' }); + hooks.beforeEach(async function () { + await authenticateSession({ username: 'admin' }); instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', diff --git a/ui/admin/tests/acceptance/roles/read-test.js b/ui/admin/tests/acceptance/roles/read-test.js index 842b383612..085d551921 100644 --- a/ui/admin/tests/acceptance/roles/read-test.js +++ b/ui/admin/tests/acceptance/roles/read-test.js @@ -5,16 +5,11 @@ import { module, test } from 'qunit'; import { visit, currentURL, click, find } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; module('Acceptance | roles | read', function (hooks) { setupApplicationTest(hooks); @@ -36,8 +31,8 @@ module('Acceptance | roles | read', function (hooks) { newRole: null, }; - hooks.beforeEach(function () { - authenticateSession({ username: 'admin' }); + hooks.beforeEach(async function () { + await authenticateSession({ username: 'admin' }); instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', diff --git a/ui/admin/tests/acceptance/roles/update-test.js b/ui/admin/tests/acceptance/roles/update-test.js index fc2489e03d..2df9a4e65f 100644 --- a/ui/admin/tests/acceptance/roles/update-test.js +++ b/ui/admin/tests/acceptance/roles/update-test.js @@ -5,15 +5,10 @@ import { module, test } from 'qunit'; import { visit, currentURL, click, fillIn, find } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { Response } from 'miragejs'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; module('Acceptance | roles | update', function (hooks) { setupApplicationTest(hooks); @@ -34,8 +29,8 @@ module('Acceptance | roles | update', function (hooks) { newRole: null, }; - hooks.beforeEach(function () { - authenticateSession({}); + hooks.beforeEach(async function () { + await authenticateSession({}); instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', diff --git a/ui/admin/tests/acceptance/scopes-test.js b/ui/admin/tests/acceptance/scopes-test.js index 952c160505..c22d0aba87 100644 --- a/ui/admin/tests/acceptance/scopes-test.js +++ b/ui/admin/tests/acceptance/scopes-test.js @@ -5,17 +5,12 @@ import { module, test } from 'qunit'; import { visit, currentURL, click, fillIn } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { Response } from 'miragejs'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; module('Acceptance | scopes', function (hooks) { setupApplicationTest(hooks); @@ -57,7 +52,7 @@ module('Acceptance | scopes', function (hooks) { project2ScopeEdit: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { @@ -92,7 +87,7 @@ module('Acceptance | scopes', function (hooks) { urls.project2ScopeEdit = `${urls.project2Scope}/edit`; // Generate resource counter getScopeCount = (type) => this.server.schema.scopes.where({ type }).length; - authenticateSession({ isGlobal: true, username: 'admin' }); + await authenticateSession({ isGlobal: true, username: 'admin' }); }); test('visiting global scope', async function (assert) { diff --git a/ui/admin/tests/acceptance/scopes/add-storage-policy-test.js b/ui/admin/tests/acceptance/scopes/add-storage-policy-test.js index 5bd0ca9a2f..8f7d83aedf 100644 --- a/ui/admin/tests/acceptance/scopes/add-storage-policy-test.js +++ b/ui/admin/tests/acceptance/scopes/add-storage-policy-test.js @@ -5,7 +5,7 @@ import { module, test } from 'qunit'; import { visit, find, click, currentURL } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { authenticateSession } from 'ember-simple-auth/test-support'; @@ -50,7 +50,7 @@ module('Acceptance | scope | add storage policy', function (hooks) { addStoragePolicy: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { featuresService = this.owner.lookup('service:features'); // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); @@ -74,7 +74,7 @@ module('Acceptance | scope | add storage policy', function (hooks) { urls.policy = `${urls.policies}/${policyOne.id}`; urls.addStoragePolicy = `${urls.orgScope}/add-storage-policy`; urls.newPolicy = `${urls.addStoragePolicy}/create`; - authenticateSession({ username: 'admin' }); + await authenticateSession({ username: 'admin' }); }); test('cannot attach policy on a scope without proper authorization', async function (assert) { diff --git a/ui/admin/tests/acceptance/scopes/delete-test.js b/ui/admin/tests/acceptance/scopes/delete-test.js index 0d12dcdc45..61a0a21898 100644 --- a/ui/admin/tests/acceptance/scopes/delete-test.js +++ b/ui/admin/tests/acceptance/scopes/delete-test.js @@ -5,16 +5,12 @@ import { module, test } from 'qunit'; import { visit, click, currentURL } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import { Response } from 'miragejs'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; module('Acceptance | scopes | delete', function (hooks) { setupApplicationTest(hooks); @@ -42,7 +38,7 @@ module('Acceptance | scopes | delete', function (hooks) { projectScopeEdit: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { @@ -60,7 +56,7 @@ module('Acceptance | scopes | delete', function (hooks) { urls.projectScope = `/scopes/${instances.scopes.project.id}`; // Generate resource counter getScopeCount = (type) => this.server.schema.scopes.where({ type }).length; - authenticateSession({ isGlobal: true }); + await authenticateSession({ isGlobal: true }); }); test('can delete scope', async function (assert) { @@ -97,7 +93,7 @@ module('Acceptance | scopes | delete', function (hooks) { await click(`[href="${urls.orgScopeEdit}"]`); await click(MANAGE_DROPDOWN_SELECTOR); await click(DELETE_ACTION_SELECTOR); - await click('.rose-dialog .rose-button-primary'); + await click(commonSelectors.MODAL_WARNING_CONFIRM_BTN); assert.strictEqual(getScopeCount('org'), orgScopeCount - 1); assert.strictEqual(currentURL(), urls.globalScope); @@ -112,7 +108,7 @@ module('Acceptance | scopes | delete', function (hooks) { await click(`[href="${urls.orgScopeEdit}"]`); await click(MANAGE_DROPDOWN_SELECTOR); await click(DELETE_ACTION_SELECTOR); - await click('.rose-dialog .rose-button-secondary'); + await click(commonSelectors.MODAL_WARNING_CANCEL_BTN); assert.strictEqual(getScopeCount('org'), orgScopeCount); assert.strictEqual(currentURL(), urls.orgScopeEdit); diff --git a/ui/admin/tests/acceptance/scopes/list-test.js b/ui/admin/tests/acceptance/scopes/list-test.js index bc727a603f..7f185dd7bb 100644 --- a/ui/admin/tests/acceptance/scopes/list-test.js +++ b/ui/admin/tests/acceptance/scopes/list-test.js @@ -5,15 +5,10 @@ import { module, test } from 'qunit'; import { visit, fillIn, waitFor } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; module('Acceptance | scopes | list', function (hooks) { setupApplicationTest(hooks); @@ -40,7 +35,7 @@ module('Acceptance | scopes | list', function (hooks) { orgScope: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org1 = this.server.create('scope', { type: 'org', @@ -60,7 +55,7 @@ module('Acceptance | scopes | list', function (hooks) { }); urls.globalScope = `/scopes/global/scopes`; urls.orgScope = `/scopes/${instances.scopes.org1.id}/scopes`; - authenticateSession({}); + await authenticateSession({}); }); test('user can search for a specifc org scope by id', async function (assert) { diff --git a/ui/admin/tests/acceptance/scopes/read-test.js b/ui/admin/tests/acceptance/scopes/read-test.js index b14e06ad74..ce1a4be6ca 100644 --- a/ui/admin/tests/acceptance/scopes/read-test.js +++ b/ui/admin/tests/acceptance/scopes/read-test.js @@ -5,16 +5,11 @@ import { module, test } from 'qunit'; import { visit, currentURL, click } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; module('Acceptance | scopes | read', function (hooks) { setupApplicationTest(hooks); @@ -44,7 +39,7 @@ module('Acceptance | scopes | read', function (hooks) { orgScopeEdit: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { @@ -58,7 +53,7 @@ module('Acceptance | scopes | read', function (hooks) { urls.orgScopeEdit = `/scopes/${instances.scopes.org.id}/edit`; features = this.owner.lookup('service:features'); featureEdition = this.owner.lookup('service:featureEdition'); - authenticateSession({ isGlobal: true, username: 'admin' }); + await authenticateSession({ isGlobal: true, username: 'admin' }); }); test('visiting org scope edit', async function (assert) { diff --git a/ui/admin/tests/acceptance/scopes/update-test.js b/ui/admin/tests/acceptance/scopes/update-test.js index 00d8670bcc..36b45c4aef 100644 --- a/ui/admin/tests/acceptance/scopes/update-test.js +++ b/ui/admin/tests/acceptance/scopes/update-test.js @@ -5,16 +5,12 @@ import { module, test } from 'qunit'; import { visit, currentURL, click, fillIn } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import { Response } from 'miragejs'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; module('Acceptance | scopes | update', function (hooks) { setupApplicationTest(hooks); @@ -35,7 +31,7 @@ module('Acceptance | scopes | update', function (hooks) { projectScope: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { @@ -55,7 +51,7 @@ module('Acceptance | scopes | update', function (hooks) { urls.orgScope = `/scopes/${instances.scopes.org.id}/scopes`; urls.orgScopeEdit = `/scopes/${instances.scopes.org.id}/edit`; urls.projectScope = `/scopes/${instances.scopes.project.id}`; - authenticateSession({ isGlobal: true }); + await authenticateSession({ isGlobal: true }); }); test('can save changes to existing scope', async function (assert) { @@ -144,8 +140,8 @@ module('Acceptance | scopes | update', function (hooks) { await fillIn('[name="name"]', 'random string'); assert.strictEqual(currentURL(), urls.orgScopeEdit); await click(`[href="${urls.globalScope}"]`); - assert.dom('.rose-dialog').exists(); - await click('.rose-dialog-footer button:first-child', 'Click Discard'); + assert.dom(commonSelectors.MODAL_WARNING).exists(); + await click(commonSelectors.MODAL_WARNING_CONFIRM_BTN); assert.strictEqual(currentURL(), urls.globalScope); assert.notEqual( @@ -165,8 +161,8 @@ module('Acceptance | scopes | update', function (hooks) { await fillIn('[name="name"]', 'random string'); assert.strictEqual(currentURL(), urls.orgScopeEdit); await click(`[href="${urls.globalScope}"]`); - assert.dom('.rose-dialog').exists(); - await click('.rose-dialog-footer button:last-child', 'Click Cancel'); + assert.dom(commonSelectors.MODAL_WARNING).exists(); + await click(commonSelectors.MODAL_WARNING_CANCEL_BTN); assert.strictEqual(currentURL(), urls.orgScopeEdit); assert.notEqual( diff --git a/ui/admin/tests/acceptance/session-recordings/list-test.js b/ui/admin/tests/acceptance/session-recordings/list-test.js index 42e077c066..8cda56987d 100644 --- a/ui/admin/tests/acceptance/session-recordings/list-test.js +++ b/ui/admin/tests/acceptance/session-recordings/list-test.js @@ -61,7 +61,7 @@ module('Acceptance | session recordings | list', function (hooks) { sessionRecording2: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', @@ -122,7 +122,7 @@ module('Acceptance | session recordings | list', function (hooks) { featuresService = this.owner.lookup('service:features'); featuresService.enable('ssh-session-recording'); - authenticateSession({}); + await authenticateSession({}); }); test('users can navigate to session-recordings with proper authorization', async function (assert) { diff --git a/ui/admin/tests/acceptance/session-recordings/read-test.js b/ui/admin/tests/acceptance/session-recordings/read-test.js index 542ce77afd..1613f69a76 100644 --- a/ui/admin/tests/acceptance/session-recordings/read-test.js +++ b/ui/admin/tests/acceptance/session-recordings/read-test.js @@ -42,7 +42,7 @@ module('Acceptance | session-recordings | read', function (hooks) { channelRecording: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', @@ -69,7 +69,7 @@ module('Acceptance | session-recordings | read', function (hooks) { urls.sessionRecordings = `${urls.globalScope}/session-recordings`; urls.sessionRecording = `${urls.sessionRecordings}/${instances.sessionRecording.id}/channels-by-connection`; urls.channelRecording = `${urls.sessionRecording}/${instances.channelRecording.id}`; - authenticateSession({ username: 'admin' }); + await authenticateSession({ username: 'admin' }); featuresService = this.owner.lookup('service:features'); }); diff --git a/ui/admin/tests/acceptance/session-recordings/session-recording/channels-by-connection/channel-test.js b/ui/admin/tests/acceptance/session-recordings/session-recording/channels-by-connection/channel-test.js index b791ac31da..e49efd784e 100644 --- a/ui/admin/tests/acceptance/session-recordings/session-recording/channels-by-connection/channel-test.js +++ b/ui/admin/tests/acceptance/session-recordings/session-recording/channels-by-connection/channel-test.js @@ -47,7 +47,7 @@ module( channelRecording: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', @@ -81,7 +81,7 @@ module( this.server.schema.sessionRecordings.all().models.length; featuresService = this.owner.lookup('service:features'); - authenticateSession({}); + await authenticateSession({}); }); test('user can navigate to a channel', async function (assert) { diff --git a/ui/admin/tests/acceptance/sessions/list-test.js b/ui/admin/tests/acceptance/sessions/list-test.js index b70d23999b..6f753fd48c 100644 --- a/ui/admin/tests/acceptance/sessions/list-test.js +++ b/ui/admin/tests/acceptance/sessions/list-test.js @@ -12,17 +12,12 @@ import { waitUntil, findAll, } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { Response } from 'miragejs'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; import { TYPE_TARGET_TCP, TYPE_TARGET_SSH } from 'api/models/target'; module('Acceptance | sessions | list', function (hooks) { @@ -56,7 +51,7 @@ module('Acceptance | sessions | list', function (hooks) { sessions: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.admin = this.server.create('user', { scopeId: 'global', @@ -103,7 +98,7 @@ module('Acceptance | sessions | list', function (hooks) { urls.projectScope = `/scopes/${instances.scopes.project.id}`; urls.sessions = `${urls.projectScope}/sessions`; - authenticateSession({ username: 'admin' }); + await authenticateSession({ username: 'admin' }); }); test('visiting sessions', async function (assert) { diff --git a/ui/admin/tests/acceptance/storage-buckets/create-test.js b/ui/admin/tests/acceptance/storage-buckets/create-test.js index ba1ae3c0ef..abcf6d9972 100644 --- a/ui/admin/tests/acceptance/storage-buckets/create-test.js +++ b/ui/admin/tests/acceptance/storage-buckets/create-test.js @@ -7,7 +7,6 @@ import { module, test } from 'qunit'; import { visit, currentURL, click, fillIn, select } from '@ember/test-helpers'; import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; -import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { Response } from 'miragejs'; import { authenticateSession } from 'ember-simple-auth/test-support'; import * as selectors from './selectors'; @@ -33,7 +32,7 @@ module('Acceptance | storage-buckets | create', function (hooks) { newStorageBucket: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', @@ -47,7 +46,7 @@ module('Acceptance | storage-buckets | create', function (hooks) { features = this.owner.lookup('service:features'); features.enable('ssh-session-recording'); - authenticateSession({ username: 'admin' }); + await authenticateSession({ username: 'admin' }); }); test('users can create a new storage bucket aws plugin type with global scope', async function (assert) { @@ -58,6 +57,11 @@ module('Acceptance | storage-buckets | create', function (hooks) { await fillIn(commonSelectors.FIELD_NAME, commonSelectors.FIELD_NAME_VALUE); await select(selectors.FIELD_SCOPE, 'global'); + await fillIn( + selectors.EDITOR_WORKER_FILTER, + selectors.EDITOR_WORKER_FILTER_VALUE, + ); + assert.dom(selectors.FIELD_BUCKET_NAME).isNotDisabled(); assert.dom(selectors.FIELD_BUCKET_PREFIX).isNotDisabled(); assert.dom(selectors.FIELD_BUCKET_NAME).doesNotHaveAttribute('readOnly'); @@ -70,6 +74,9 @@ module('Acceptance | storage-buckets | create', function (hooks) { assert.strictEqual(storageBucket.name, commonSelectors.FIELD_NAME_VALUE); assert.strictEqual(storageBucket.scopeId, 'global'); + assert + .dom(selectors.READONLY_WORKER_FILTER) + .hasText(selectors.EDITOR_WORKER_FILTER_VALUE); assert.strictEqual(getStorageBucketCount(), storageBucketCount + 1); }); @@ -81,6 +88,11 @@ module('Acceptance | storage-buckets | create', function (hooks) { await fillIn(commonSelectors.FIELD_NAME, commonSelectors.FIELD_NAME_VALUE); await select(selectors.FIELD_SCOPE, instances.scopes.org.id); + await fillIn( + selectors.EDITOR_WORKER_FILTER, + selectors.EDITOR_WORKER_FILTER_VALUE, + ); + assert.dom(selectors.FIELD_BUCKET_NAME).isNotDisabled(); assert.dom(selectors.FIELD_BUCKET_PREFIX).isNotDisabled(); assert.dom(selectors.FIELD_BUCKET_NAME).doesNotHaveAttribute('readOnly'); @@ -120,6 +132,11 @@ module('Acceptance | storage-buckets | create', function (hooks) { ); await fillIn(selectors.FIELD_ACCESS_KEY, selectors.FIELD_ACCESS_KEY_VALUE); await fillIn(selectors.FIELD_SECRET_KEY, selectors.FIELD_SECRET_KEY_VALUE); + await fillIn( + selectors.EDITOR_WORKER_FILTER, + selectors.EDITOR_WORKER_FILTER_VALUE, + ); + await click(commonSelectors.SAVE_BTN); // Assertions @@ -148,12 +165,14 @@ module('Acceptance | storage-buckets | create', function (hooks) { await fillIn(commonSelectors.FIELD_NAME, commonSelectors.FIELD_NAME_VALUE); // There are 2 credential types - assert - .dom(`${selectors.GROUP_CREDENTIAL_TYPE} .hds-form-radio-card`) - .exists({ count: 2 }); + assert.dom(selectors.GROUP_CREDENTIAL_TYPE).exists({ count: 2 }); await click(selectors.FIELD_DYNAMIC_CREDENTIAL); await fillIn(selectors.FIELD_ROLE_ARN, selectors.FIELD_ROLE_ARN_VALUE); + await fillIn( + selectors.EDITOR_WORKER_FILTER, + selectors.EDITOR_WORKER_FILTER_VALUE, + ); await click(commonSelectors.SAVE_BTN); const storageBucket = this.server.schema.storageBuckets.findBy({ @@ -172,13 +191,15 @@ module('Acceptance | storage-buckets | create', function (hooks) { await click(`[href="${urls.newStorageBucket}"]`); await fillIn(commonSelectors.FIELD_NAME, commonSelectors.FIELD_NAME_VALUE); - assert - .dom(`${selectors.GROUP_CREDENTIAL_TYPE} .hds-form-radio-card`) - .exists({ count: 2 }); + assert.dom(selectors.GROUP_CREDENTIAL_TYPE).exists({ count: 2 }); await click(selectors.FIELD_STATIC_CREDENTIAL); await fillIn(selectors.FIELD_ACCESS_KEY, selectors.FIELD_ACCESS_KEY_VALUE); await fillIn(selectors.FIELD_SECRET_KEY, selectors.FIELD_SECRET_KEY_VALUE); + await fillIn( + selectors.EDITOR_WORKER_FILTER, + selectors.EDITOR_WORKER_FILTER_VALUE, + ); await click(commonSelectors.SAVE_BTN); @@ -212,6 +233,10 @@ module('Acceptance | storage-buckets | create', function (hooks) { ); await fillIn(selectors.FIELD_ACCESS_KEY, selectors.FIELD_ACCESS_KEY_VALUE); await fillIn(selectors.FIELD_SECRET_KEY, selectors.FIELD_SECRET_KEY_VALUE); + await fillIn( + selectors.EDITOR_WORKER_FILTER, + selectors.EDITOR_WORKER_FILTER_VALUE, + ); await click(commonSelectors.SAVE_BTN); @@ -268,8 +293,11 @@ module('Acceptance | storage-buckets | create', function (hooks) { await visit(urls.storageBuckets); await click(`[href="${urls.newStorageBucket}"]`); + await fillIn( + selectors.EDITOR_WORKER_FILTER, + selectors.EDITOR_WORKER_FILTER_VALUE, + ); await click(commonSelectors.SAVE_BTN); - await a11yAudit(); assert .dom(commonSelectors.ALERT_TOAST_BODY) diff --git a/ui/admin/tests/acceptance/storage-buckets/delete-test.js b/ui/admin/tests/acceptance/storage-buckets/delete-test.js index d52b4e51ef..6c752f75d1 100644 --- a/ui/admin/tests/acceptance/storage-buckets/delete-test.js +++ b/ui/admin/tests/acceptance/storage-buckets/delete-test.js @@ -4,11 +4,12 @@ */ import { module, test } from 'qunit'; import { visit, click, currentURL } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { Response } from 'miragejs'; import { authenticateSession } from 'ember-simple-auth/test-support'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; module('Acceptance | storage-buckets | delete', function (hooks) { setupApplicationTest(hooks); @@ -21,10 +22,6 @@ module('Acceptance | storage-buckets | delete', function (hooks) { const DROPDOWN_BUTTON_SELECTOR = '.hds-dropdown-toggle-icon'; const DELETE_DROPDOWN_SELECTOR = '.hds-dropdown-list-item [type="button"]'; - const DIALOG_DELETE_BTN_SELECTOR = '.rose-dialog .rose-button-primary'; - const DIALOG_CANCEL_BTN_SELECTOR = '.rose-dialog .rose-button-secondary'; - const DIALOG_MESSAGE_SELECTOR = '.rose-dialog-body'; - const DIALOG_TITLE_SELECTOR = '.rose-dialog-header'; const NOTIFICATION_MSG_SELECTOR = '[data-test-toast-notification] .hds-alert__description'; const NOTIFICATION_MSG_TEXT = 'Deleted successfully.'; @@ -42,7 +39,7 @@ module('Acceptance | storage-buckets | delete', function (hooks) { storageBuckets: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', @@ -56,7 +53,7 @@ module('Acceptance | storage-buckets | delete', function (hooks) { getStorageBucketCount = () => this.server.schema.storageBuckets.all().models.length; - authenticateSession({}); + await authenticateSession({}); intl = this.owner.lookup('service:intl'); features = this.owner.lookup('service:features'); features.enable('ssh-session-recording'); @@ -85,10 +82,10 @@ module('Acceptance | storage-buckets | delete', function (hooks) { await click(DELETE_DROPDOWN_SELECTOR); assert - .dom(DIALOG_DELETE_BTN_SELECTOR) + .dom(commonSelectors.MODAL_WARNING_CONFIRM_BTN) .hasText(intl.t('resources.storage-bucket.actions.delete')); - await click(DIALOG_DELETE_BTN_SELECTOR); + await click(commonSelectors.MODAL_WARNING_CONFIRM_BTN); assert.dom(NOTIFICATION_MSG_SELECTOR).hasText(NOTIFICATION_MSG_TEXT); assert.strictEqual(currentURL(), urls.storageBuckets); @@ -106,21 +103,21 @@ module('Acceptance | storage-buckets | delete', function (hooks) { await click(DELETE_DROPDOWN_SELECTOR); assert - .dom(DIALOG_TITLE_SELECTOR) + .dom(commonSelectors.MODAL_WARNING_TITLE) .hasText( intl.t( 'resources.storage-bucket.questions.delete-storage-bucket.title', ), ); assert - .dom(DIALOG_MESSAGE_SELECTOR) + .dom(commonSelectors.MODAL_WARNING_MESSAGE) .hasText( intl.t( 'resources.storage-bucket.questions.delete-storage-bucket.message', ), ); - await click(DIALOG_CANCEL_BTN_SELECTOR); + await click(commonSelectors.MODAL_WARNING_CANCEL_BTN); assert.strictEqual(currentURL(), urls.storageBuckets); assert.strictEqual(getStorageBucketCount(), storageBucketCount); diff --git a/ui/admin/tests/acceptance/storage-buckets/list-test.js b/ui/admin/tests/acceptance/storage-buckets/list-test.js index 1bb6690284..8bdad990dc 100644 --- a/ui/admin/tests/acceptance/storage-buckets/list-test.js +++ b/ui/admin/tests/acceptance/storage-buckets/list-test.js @@ -34,7 +34,7 @@ module('Acceptance | storage-buckets | list', function (hooks) { storageBucket: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', @@ -45,7 +45,7 @@ module('Acceptance | storage-buckets | list', function (hooks) { intl = this.owner.lookup('service:intl'); - authenticateSession({}); + await authenticateSession({}); featuresService = this.owner.lookup('service:features'); }); diff --git a/ui/admin/tests/acceptance/storage-buckets/read-test.js b/ui/admin/tests/acceptance/storage-buckets/read-test.js index 985732dc6f..1a9cfc9c45 100644 --- a/ui/admin/tests/acceptance/storage-buckets/read-test.js +++ b/ui/admin/tests/acceptance/storage-buckets/read-test.js @@ -34,7 +34,7 @@ module('Acceptance | storage-buckets | read', function (hooks) { unknownStorageBucket: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', @@ -50,7 +50,7 @@ module('Acceptance | storage-buckets | read', function (hooks) { features = this.owner.lookup('service:features'); features.enable('ssh-session-recording'); - authenticateSession({ username: 'admin' }); + await authenticateSession({ username: 'admin' }); }); test('visiting a storage bucket', async function (assert) { @@ -71,7 +71,9 @@ module('Acceptance | storage-buckets | read', function (hooks) { await click(commonSelectors.HREF(urls.storageBuckets)); - assert.dom(commonSelectors.TABLE_FIRST_ROW_RESOURCE_LINK).doesNotExist(); + assert + .dom(commonSelectors.TABLE_RESOURCE_LINK(urls.storageBucket)) + .doesNotExist(); }); test('visiting an unknown storage bucket displays 404 message', async function (assert) { @@ -83,7 +85,7 @@ module('Acceptance | storage-buckets | read', function (hooks) { .hasText(commonSelectors.RESOURCE_NOT_FOUND_VALUE); }); - test('users can navigate to storage bucket and incorrect url autocorrects', async function (assert) { + test('users can navigate to storage bucket and incorrect url auto-corrects', async function (assert) { const incorrectUrl = `/scopes/${instances.scopes.org.id}/storage-buckets/${instances.storageBucket.id}`; await visit(incorrectUrl); diff --git a/ui/admin/tests/acceptance/storage-buckets/selectors.js b/ui/admin/tests/acceptance/storage-buckets/selectors.js index 08eef815a4..2448d7a45f 100644 --- a/ui/admin/tests/acceptance/storage-buckets/selectors.js +++ b/ui/admin/tests/acceptance/storage-buckets/selectors.js @@ -14,7 +14,7 @@ export const FIELD_BUCKET_NAME_VALUE = 'Test Bucket name'; export const FIELD_BUCKET_NAME_ERROR = '[data-test-bucket-name-error]'; export const FIELD_BUCKET_PREFIX = '[name=bucket_prefix]'; export const FIELD_REGION = '[name=region]'; -export const GROUP_CREDENTIAL_TYPE = '[name=credential_type]'; +export const GROUP_CREDENTIAL_TYPE = '[name=Type]'; export const FIELD_STATIC_CREDENTIAL = '[value=static]'; export const FIELD_DYNAMIC_CREDENTIAL = '[value=dynamic]'; export const FIELD_ROLE_ARN = '[name=role_arn]'; @@ -29,6 +29,11 @@ export const FIELD_SECRET_KEY_EDIT_BTN = '[data-test-secret-access-key] [type=button]'; export const FIELD_WORKER_FILTER = '[name=worker_filter]'; export const FIELD_WORKER_FILTER_ERROR = '[data-test-worker-filter-error]'; +export const READONLY_WORKER_FILTER = '[data-test-worker-filter]'; +export const EDITOR_WORKER_FILTER = + '[data-test-code-editor-field-editor] textarea'; +export const EDITOR_WORKER_FILTER_VALUE = '"dev" in "/tags/env"'; +export const TOAST = '[role=alert] div'; export const TOAST_SUCCESSFUL_VALUE = 'Saved successfully.'; export const TABLE_FIRST_ROW_ACTION_DROPDOWN = diff --git a/ui/admin/tests/acceptance/storage-buckets/update-test.js b/ui/admin/tests/acceptance/storage-buckets/update-test.js index eaad42e233..89df6e3ec6 100644 --- a/ui/admin/tests/acceptance/storage-buckets/update-test.js +++ b/ui/admin/tests/acceptance/storage-buckets/update-test.js @@ -5,10 +5,9 @@ import { module, test } from 'qunit'; import { visit, click, fillIn } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { Response } from 'miragejs'; -import { authenticateSession } from 'ember-simple-auth/test-support'; import * as selectors from './selectors'; import * as commonSelectors from 'admin/tests/helpers/selectors'; @@ -23,15 +22,16 @@ module('Acceptance | storage-buckets | update', function (hooks) { global: null, }, storageBucket: null, + storageBucketMinio: null, }; const urls = { - globalScope: null, storageBuckets: null, storageBucket: null, + storageBucketMinio: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.storageBucket = this.server.create('storage-bucket', { scope: instances.scopes.global, @@ -40,20 +40,18 @@ module('Acceptance | storage-buckets | update', function (hooks) { scope: instances.scopes.global, plugin: { name: 'minio' }, }); - urls.globalScope = `/scopes/global`; - urls.storageBuckets = `${urls.globalScope}/storage-buckets`; + urls.storageBuckets = `/scopes/global/storage-buckets`; urls.storageBucket = `${urls.storageBuckets}/${instances.storageBucket.id}`; urls.storageBucketMinio = `${urls.storageBuckets}/${instances.storageBucketMinio.id}`; features = this.owner.lookup('service:features'); features.enable('ssh-session-recording'); - authenticateSession({}); }); test('can save changes to an existing storage-bucket', async function (assert) { await visit(urls.storageBuckets); - await click(`[href="${urls.storageBucket}"]`); + await click(commonSelectors.HREF(urls.storageBucket)); await click(commonSelectors.EDIT_BTN, 'Click edit mode'); await fillIn(commonSelectors.FIELD_NAME, commonSelectors.FIELD_NAME_VALUE); await click(commonSelectors.SAVE_BTN, 'Click save'); @@ -72,7 +70,7 @@ module('Acceptance | storage-buckets | update', function (hooks) { const name = instances.storageBucket.name; await visit(urls.storageBuckets); - await click(`[href="${urls.storageBucket}"]`); + await click(commonSelectors.HREF(urls.storageBucket)); await click(commonSelectors.EDIT_BTN, 'Click edit mode'); await fillIn(commonSelectors.FIELD_NAME, commonSelectors.FIELD_NAME_VALUE); await click(commonSelectors.CANCEL_BTN, 'Click cancel'); @@ -84,7 +82,7 @@ module('Acceptance | storage-buckets | update', function (hooks) { test('can save changes to access key fields', async function (assert) { await visit(urls.storageBuckets); - await click(`[href="${urls.storageBucket}"]`); + await click(commonSelectors.HREF(urls.storageBucket)); assert.dom(selectors.FIELD_ACCESS_KEY_EDIT_BTN).exists(); assert.dom(selectors.FIELD_SECRET_KEY_EDIT_BTN).exists(); @@ -119,7 +117,7 @@ module('Acceptance | storage-buckets | update', function (hooks) { test('can cancel changes to access key fields', async function (assert) { await visit(urls.storageBuckets); - await click(`[href="${urls.storageBucket}"]`); + await click(commonSelectors.HREF(urls.storageBucket)); await click(commonSelectors.EDIT_BTN, 'Click edit mode'); await click(selectors.FIELD_ACCESS_KEY_EDIT_BTN); await click(selectors.FIELD_SECRET_KEY_EDIT_BTN); @@ -167,7 +165,10 @@ module('Acceptance | storage-buckets | update', function (hooks) { await click(`[href="${urls.storageBucket}"]`); await click(commonSelectors.EDIT_BTN, 'Activate edit mode'); - await fillIn(selectors.FIELD_WORKER_FILTER, 'random string'); + await fillIn( + selectors.EDITOR_WORKER_FILTER, + selectors.EDITOR_WORKER_FILTER_VALUE, + ); await click(commonSelectors.SAVE_BTN); assert @@ -184,7 +185,7 @@ module('Acceptance | storage-buckets | update', function (hooks) { test('user cannot edit scope, provider, bucket name, bucket prefix and region fields in a Amazon S3 storage bucket form', async function (assert) { await visit(urls.storageBuckets); - await click(`[href="${urls.storageBucket}"]`); + await click(commonSelectors.HREF(urls.storageBucket)); assert.dom(selectors.FIELD_BUCKET_NAME).isDisabled(); assert.dom(selectors.FIELD_BUCKET_PREFIX).isDisabled(); @@ -204,7 +205,7 @@ module('Acceptance | storage-buckets | update', function (hooks) { test('user cannot edit scope, provider, endpoint_url, bucket name or region fields in a MinIO storage bucket', async function (assert) { await visit(urls.storageBuckets); - await click(`[href="${urls.storageBucketMinio}"]`); + await click(commonSelectors.HREF(urls.storageBucketMinio)); assert.dom(selectors.FIELD_BUCKET_NAME).isDisabled(); @@ -216,4 +217,17 @@ module('Acceptance | storage-buckets | update', function (hooks) { assert.dom(selectors.FIELD_BUCKET_NAME).hasAttribute('readOnly'); assert.dom(selectors.FIELD_REGION).hasAttribute('readOnly'); }); + + test('user sees an editable code editor while updating and readonly code block before/after', async function (assert) { + await visit(urls.storageBuckets); + await click(commonSelectors.HREF(urls.storageBucketMinio)); + + assert.dom(selectors.READONLY_WORKER_FILTER).isVisible(); + assert.dom(selectors.EDITOR_WORKER_FILTER).doesNotExist(); + + await click(commonSelectors.EDIT_BTN, 'Click edit mode'); + + assert.dom(selectors.EDITOR_WORKER_FILTER).isVisible(); + assert.dom(selectors.READONLY_WORKER_FILTER).doesNotExist(); + }); }); diff --git a/ui/admin/tests/acceptance/targets/brokered-credential-sources-test.js b/ui/admin/tests/acceptance/targets/brokered-credential-sources-test.js index a658d9341f..e154436399 100644 --- a/ui/admin/tests/acceptance/targets/brokered-credential-sources-test.js +++ b/ui/admin/tests/acceptance/targets/brokered-credential-sources-test.js @@ -4,8 +4,8 @@ */ import { module, test } from 'qunit'; -import { visit, find, findAll, click, currentURL } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { visit, findAll, click, currentURL } from '@ember/test-helpers'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { Response } from 'miragejs'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; @@ -49,7 +49,7 @@ module('Acceptance | targets | brokered credential sources', function (hooks) { brokeredCredentialSources: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { featuresService = this.owner.lookup('service:features'); // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); @@ -117,7 +117,7 @@ module('Acceptance | targets | brokered credential sources', function (hooks) { this.server.schema.credentials.all().models.length; credentialSourceCount = getCredentialLibraryCount() + getCredentialCount(); - authenticateSession({ username: 'admin' }); + await authenticateSession({ username: 'admin' }); }); test('visiting target brokered credential sources', async function (assert) { @@ -167,7 +167,7 @@ module('Acceptance | targets | brokered credential sources', function (hooks) { }); await visit(urls.addBrokeredCredentialSources); assert.strictEqual(findAll('tbody tr').length, credentialSourceCount); - assert.dom('.rose-message-title').doesNotExist(); + assert.dom('.hds-application-state__title').doesNotExist(); }); test('displays list of brokered credential sources with only credential libraries available', async function (assert) { @@ -176,15 +176,14 @@ module('Acceptance | targets | brokered credential sources', function (hooks) { }); await visit(urls.addBrokeredCredentialSources); assert.strictEqual(findAll('tbody tr').length, getCredentialLibraryCount()); - assert.dom('.rose-message-title').doesNotExist(); + assert.dom('.hds-application-state__title').doesNotExist(); }); test('displays no brokered credential sources message when none available', async function (assert) { await visit(urls.addBrokeredCredentialSources); - assert.strictEqual( - find('.rose-message-title').textContent.trim(), - 'No Brokered Credential Sources Available', - ); + assert + .dom('.hds-application-state__title') + .hasText('No Brokered Credential Sources Available'); }); test('when no brokered credential sources available, button routes to add brokered credential sources', async function (assert) { @@ -193,7 +192,7 @@ module('Acceptance | targets | brokered credential sources', function (hooks) { }); await visit(urls.brokeredCredentialSources); // Click on the rose message link - await click(find('.rose-message > .rose-message-body > a')); + await click('.hds-application-state__footer .hds-link-standalone'); assert.strictEqual(currentURL(), urls.addBrokeredCredentialSources); }); diff --git a/ui/admin/tests/acceptance/targets/create-alias-test.js b/ui/admin/tests/acceptance/targets/create-alias-test.js index 8ec2ce2657..7d1b3b92b3 100644 --- a/ui/admin/tests/acceptance/targets/create-alias-test.js +++ b/ui/admin/tests/acceptance/targets/create-alias-test.js @@ -50,7 +50,7 @@ module('Acceptance | targets | create-alias', function (hooks) { target: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { const { owner } = getContext(); featuresService = owner.lookup('service:features'); instances.scopes.global = this.server.create('scope', { id: 'global' }); @@ -78,7 +78,7 @@ module('Acceptance | targets | create-alias', function (hooks) { getAliasCount = () => this.server.schema.aliases.all().models.length; - authenticateSession({ username: 'admin' }); + await authenticateSession({ username: 'admin' }); }); test('users can create a new alias for a target of TCP type', async function (assert) { diff --git a/ui/admin/tests/acceptance/targets/create-test.js b/ui/admin/tests/acceptance/targets/create-test.js index 73d8458831..0baf0fceb2 100644 --- a/ui/admin/tests/acceptance/targets/create-test.js +++ b/ui/admin/tests/acceptance/targets/create-test.js @@ -12,15 +12,10 @@ import { fillIn, getContext, } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { Response } from 'miragejs'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; import { TYPE_TARGET_TCP, TYPE_TARGET_SSH } from 'api/models/target'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; @@ -54,7 +49,7 @@ module('Acceptance | targets | create', function (hooks) { newSSHTarget: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { const { owner } = getContext(); featuresService = owner.lookup('service:features'); instances.scopes.global = this.server.create('scope', { id: 'global' }); @@ -84,13 +79,13 @@ module('Acceptance | targets | create', function (hooks) { this.server.schema.targets.where({ type: TYPE_TARGET_SSH }).models.length; getTCPTargetCount = () => this.server.schema.targets.where({ type: TYPE_TARGET_TCP }).models.length; - authenticateSession({}); + await authenticateSession({}); }); test('defaults to type `ssh` when no query param provided', async function (assert) { featuresService.enable('ssh-target'); await visit(urls.newTarget); - assert.strictEqual(find('[name="type"]:checked').value, TYPE_TARGET_SSH); + assert.strictEqual(find('[name="Type"]:checked').value, TYPE_TARGET_SSH); }); test('can create a type `ssh` target', async function (assert) { diff --git a/ui/admin/tests/acceptance/targets/delete-test.js b/ui/admin/tests/acceptance/targets/delete-test.js index f7d25e21f0..f10f69eb3f 100644 --- a/ui/admin/tests/acceptance/targets/delete-test.js +++ b/ui/admin/tests/acceptance/targets/delete-test.js @@ -5,16 +5,12 @@ import { module, test } from 'qunit'; import { visit, click, currentURL } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import { Response } from 'miragejs'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; module('Acceptance | targets | delete', function (hooks) { setupApplicationTest(hooks); @@ -43,7 +39,7 @@ module('Acceptance | targets | delete', function (hooks) { newTarget: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { @@ -65,7 +61,7 @@ module('Acceptance | targets | delete', function (hooks) { urls.newTarget = `${urls.targets}/new`; // Generate resource counter getTargetCount = () => this.server.schema.targets.all().models.length; - authenticateSession({}); + await authenticateSession({}); }); test('can delete target', async function (assert) { @@ -87,7 +83,7 @@ module('Acceptance | targets | delete', function (hooks) { await click(`[href="${urls.target}"]`); await click(MANAGE_DROPDOWN_SELECTOR); await click(DELETE_ACTION_SELECTOR); - await click('.rose-dialog .rose-button-primary'); + await click(commonSelectors.MODAL_WARNING_CONFIRM_BTN); assert .dom('[data-test-toast-notification] .hds-alert__description') @@ -105,7 +101,7 @@ module('Acceptance | targets | delete', function (hooks) { await click(`[href="${urls.target}"]`); await click(MANAGE_DROPDOWN_SELECTOR); await click(DELETE_ACTION_SELECTOR); - await click('.rose-dialog .rose-button-secondary'); + await click(commonSelectors.MODAL_WARNING_CANCEL_BTN); assert.strictEqual(getTargetCount(), targetCount); assert.strictEqual(currentURL(), urls.target); diff --git a/ui/admin/tests/acceptance/targets/enable-session-recording-test.js b/ui/admin/tests/acceptance/targets/enable-session-recording-test.js index 714ff9c9f8..0079fb84a8 100644 --- a/ui/admin/tests/acceptance/targets/enable-session-recording-test.js +++ b/ui/admin/tests/acceptance/targets/enable-session-recording-test.js @@ -5,7 +5,7 @@ import { module, test } from 'qunit'; import { visit, find, click, currentURL } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { authenticateSession } from 'ember-simple-auth/test-support'; @@ -53,7 +53,7 @@ module('Acceptance | targets | enable session recording', function (hooks) { newStorageBucket: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { featuresService = this.owner.lookup('service:features'); // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); @@ -89,7 +89,7 @@ module('Acceptance | targets | enable session recording', function (hooks) { urls.newStorageBucket = `${urls.enableSessionRecording}/create-storage-bucket`; urls.storageBucket = `${urls.storageBuckets}/${storageBucketOne.id}`; - authenticateSession({ username: 'admin' }); + await authenticateSession({ username: 'admin' }); }); test('cannot enable session recording for a target without proper authorization', async function (assert) { diff --git a/ui/admin/tests/acceptance/targets/enable-session-recording/create-storage-bucket-test.js b/ui/admin/tests/acceptance/targets/enable-session-recording/create-storage-bucket-test.js index 03d9470f79..bdb9049cc8 100644 --- a/ui/admin/tests/acceptance/targets/enable-session-recording/create-storage-bucket-test.js +++ b/ui/admin/tests/acceptance/targets/enable-session-recording/create-storage-bucket-test.js @@ -7,7 +7,6 @@ import { module, test } from 'qunit'; import { visit, currentURL, click, fillIn } from '@ember/test-helpers'; import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; -import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { Response } from 'miragejs'; import { authenticateSession } from 'ember-simple-auth/test-support'; import { TYPE_TARGET_SSH } from 'api/models/target'; @@ -30,6 +29,9 @@ module( const NAME_FIELD_TEXT = 'random string'; const BUCKET_NAME_FIELD_SELECTOR = '[name="bucket_name"]'; const BUCKET_PREFIX_FIELD_SELECTOR = '[name="bucket_prefix"]'; + const EDITOR_WORKER_FILTER = + '[data-test-code-editor-field-editor] textarea'; + const EDITOR_WORKER_FILTER_VALUE = '"dev" in "/tags/env"'; const instances = { scopes: { @@ -47,7 +49,7 @@ module( newStorageBucket: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', @@ -74,7 +76,7 @@ module( features = this.owner.lookup('service:features'); features.enable('ssh-session-recording'); - authenticateSession({ username: 'admin' }); + await authenticateSession({ username: 'admin' }); }); test('users can create a new storage bucket with global scope', async function (assert) { @@ -84,6 +86,7 @@ module( await click(`[href="${urls.newStorageBucket}"]`); await fillIn(NAME_FIELD_SELECTOR, NAME_FIELD_TEXT); await click('[value="global"]'); + await fillIn(EDITOR_WORKER_FILTER, EDITOR_WORKER_FILTER_VALUE); assert.dom(BUCKET_NAME_FIELD_SELECTOR).isNotDisabled(); assert.dom(BUCKET_PREFIX_FIELD_SELECTOR).isNotDisabled(); @@ -107,6 +110,7 @@ module( await click(`[href="${urls.newStorageBucket}"]`); await fillIn(NAME_FIELD_SELECTOR, NAME_FIELD_TEXT); await click(`[value="${instances.scopes.org.scope.id}"]`); + await fillIn(EDITOR_WORKER_FILTER, EDITOR_WORKER_FILTER_VALUE); assert.dom(BUCKET_NAME_FIELD_SELECTOR).isNotDisabled(); assert.dom(BUCKET_PREFIX_FIELD_SELECTOR).isNotDisabled(); @@ -158,8 +162,8 @@ module( await visit(urls.enableSessionRecording); await click(`[href="${urls.newStorageBucket}"]`); + await fillIn(EDITOR_WORKER_FILTER, EDITOR_WORKER_FILTER_VALUE); await click(SAVE_BTN_SELECTOR); - await a11yAudit(); assert.dom(ALERT_TEXT_SELECTOR).hasText('The request was invalid.'); assert.dom(FIELD_ERROR_TEXT_SELECTOR).hasText('Name is required.'); diff --git a/ui/admin/tests/acceptance/targets/enable-session-recording/index-test.js b/ui/admin/tests/acceptance/targets/enable-session-recording/index-test.js index ef2c535170..c42520dcc1 100644 --- a/ui/admin/tests/acceptance/targets/enable-session-recording/index-test.js +++ b/ui/admin/tests/acceptance/targets/enable-session-recording/index-test.js @@ -5,7 +5,7 @@ import { module, test } from 'qunit'; import { visit, find, click, currentURL } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { authenticateSession } from 'ember-simple-auth/test-support'; @@ -56,7 +56,7 @@ module( newStorageBucket: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { featuresService = this.owner.lookup('service:features'); // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); @@ -94,7 +94,7 @@ module( intl = this.owner.lookup('service:intl'); - authenticateSession({ username: 'admin' }); + await authenticateSession({ username: 'admin' }); }); test('cannot enable session recording for a target without proper authorization', async function (assert) { diff --git a/ui/admin/tests/acceptance/targets/host-sources-test.js b/ui/admin/tests/acceptance/targets/host-sources-test.js index ada073880b..69cf5b315f 100644 --- a/ui/admin/tests/acceptance/targets/host-sources-test.js +++ b/ui/admin/tests/acceptance/targets/host-sources-test.js @@ -5,16 +5,12 @@ import { module, test } from 'qunit'; import { visit, currentURL, click } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { Response } from 'miragejs'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; module('Acceptance | targets | host-sources', function (hooks) { setupApplicationTest(hooks); @@ -45,7 +41,7 @@ module('Acceptance | targets | host-sources', function (hooks) { targetAddHostSources: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { @@ -85,10 +81,11 @@ module('Acceptance | targets | host-sources', function (hooks) { urls.targetHostSources = `${urls.target}/host-sources`; urls.targetAddHostSources = `${urls.target}/add-host-sources`; urls.hostSet = `${urls.projectScope}/host-catalogs/${instances.hostCatalog.id}/host-sets/${instances.hostCatalog.hostSetIds[0]}`; - // Generate resource counter - getTargetHostSetCount = () => - this.server.schema.targets.first().hostSets.models.length; - authenticateSession({ username: 'admin' }); + urls.unknownHostSet = + // Generate resource counter + getTargetHostSetCount = () => + this.server.schema.targets.first().hostSets.models.length; + await authenticateSession({ username: 'admin' }); }); test('visiting target host sources', async function (assert) { @@ -119,7 +116,13 @@ module('Acceptance | targets | host-sources', function (hooks) { await click(`[href="${urls.targetHostSources}"]`); - assert.dom('.rose-table-body tr:first-child a').doesNotExist(); + assert + .dom( + commonSelectors.TABLE_RESOURCE_LINK( + instances.hostCatalogPlugin.hostSets[0], + ), + ) + .doesNotExist(); }); test('can remove a host set', async function (assert) { @@ -281,8 +284,8 @@ module('Acceptance | targets | host-sources', function (hooks) { await click('tbody label'); await click('form [type="submit"]'); - assert.dom('.rose-dialog').isVisible(); - await click('.rose-dialog-footer .rose-button-primary', 'Remove resources'); + assert.dom(commonSelectors.MODAL_WARNING).isVisible(); + await click(commonSelectors.MODAL_WARNING_CONFIRM_BTN); assert.strictEqual( this.server.schema.targets.find(target.id).address, @@ -317,11 +320,8 @@ module('Acceptance | targets | host-sources', function (hooks) { await click('tbody label'); await click('form [type="submit"]'); - assert.dom('.rose-dialog').isVisible(); - await click( - '.rose-dialog-footer .rose-button-secondary', - 'Remove resources', - ); + assert.dom(commonSelectors.MODAL_WARNING).isVisible(); + await click(commonSelectors.MODAL_WARNING_CANCEL_BTN); assert.strictEqual( this.server.schema.targets.find(target.id).address, diff --git a/ui/admin/tests/acceptance/targets/injected-application-credential-sources-test.js b/ui/admin/tests/acceptance/targets/injected-application-credential-sources-test.js index 219832047d..ae658c0c1e 100644 --- a/ui/admin/tests/acceptance/targets/injected-application-credential-sources-test.js +++ b/ui/admin/tests/acceptance/targets/injected-application-credential-sources-test.js @@ -4,8 +4,8 @@ */ import { module, test } from 'qunit'; -import { visit, find, findAll, click, currentURL } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { visit, findAll, click, currentURL } from '@ember/test-helpers'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { Response } from 'miragejs'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; @@ -49,7 +49,7 @@ module( injectedApplicationCredentialSources: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { @@ -116,7 +116,7 @@ module( credentialSourceCount = getCredentialLibraryCount() + getCredentialCount(); - authenticateSession({ username: 'admin' }); + await authenticateSession({ username: 'admin' }); }); test('visiting target injected application credential sources', async function (assert) { @@ -163,7 +163,7 @@ module( }); await visit(urls.addInjectedApplicationCredentialSources); assert.strictEqual(findAll('tbody tr').length, credentialSourceCount); - assert.dom('.rose-message-title').doesNotExist(); + assert.dom('.hds-application-state__title').doesNotExist(); }); test('displays list of injected application credential sources with only credential libraries available', async function (assert) { @@ -177,15 +177,14 @@ module( findAll('tbody tr').length, getCredentialLibraryCount(), ); - assert.dom('.rose-message-title').doesNotExist(); + assert.dom('.hds-application-state__title').doesNotExist(); }); test('displays no injected application credential sources message when none available', async function (assert) { await visit(urls.addInjectedApplicationCredentialSources); - assert.strictEqual( - find('.rose-message-title').textContent.trim(), - 'No Injected Application Credential Sources Available', - ); + assert + .dom('.hds-application-state__title') + .hasText('No Injected Application Credential Sources Available'); }); test('when no injected application credential sources available, button routes to add injected application credential sources', async function (assert) { @@ -194,7 +193,7 @@ module( }); await visit(urls.injectedApplicationCredentialSources); // Click on the rose message link - await click(find('.rose-message > .rose-message-body > a')); + await click('.hds-application-state__footer .hds-link-standalone'); assert.strictEqual( currentURL(), urls.addInjectedApplicationCredentialSources, diff --git a/ui/admin/tests/acceptance/targets/list-test.js b/ui/admin/tests/acceptance/targets/list-test.js index 20f65b740b..ee4d379f4c 100644 --- a/ui/admin/tests/acceptance/targets/list-test.js +++ b/ui/admin/tests/acceptance/targets/list-test.js @@ -11,15 +11,10 @@ import { waitFor, currentRouteName, } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; import { TYPE_TARGET_TCP, TYPE_TARGET_SSH } from 'api/models/target'; import { STATUS_SESSION_ACTIVE } from 'api/models/session'; @@ -58,7 +53,7 @@ module('Acceptance | targets | list', function (hooks) { sshTarget: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', @@ -90,7 +85,7 @@ module('Acceptance | targets | list', function (hooks) { const featuresService = this.owner.lookup('service:features'); featuresService.enable('ssh-target'); - authenticateSession({}); + await authenticateSession({}); }); test('can navigate to targets with proper authorization', async function (assert) { diff --git a/ui/admin/tests/acceptance/targets/manage-alias-test.js b/ui/admin/tests/acceptance/targets/manage-alias-test.js index 6075efcf8a..81ee1648ad 100644 --- a/ui/admin/tests/acceptance/targets/manage-alias-test.js +++ b/ui/admin/tests/acceptance/targets/manage-alias-test.js @@ -5,7 +5,7 @@ import { module, test } from 'qunit'; import { visit, currentURL, click } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { authenticateSession } from 'ember-simple-auth/test-support'; @@ -45,7 +45,7 @@ module('Acceptance | targets | manage-alias', function (hooks) { aliases: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { @@ -90,7 +90,7 @@ module('Acceptance | targets | manage-alias', function (hooks) { getAliasCount = () => this.server.schema.aliases.all().models.length; - authenticateSession({ username: 'admin' }); + await authenticateSession({ username: 'admin' }); }); test('clicking on manage should take you to manage page', async function (assert) { diff --git a/ui/admin/tests/acceptance/targets/read-test.js b/ui/admin/tests/acceptance/targets/read-test.js index d88def7a89..1292b29461 100644 --- a/ui/admin/tests/acceptance/targets/read-test.js +++ b/ui/admin/tests/acceptance/targets/read-test.js @@ -5,17 +5,13 @@ import { module, test } from 'qunit'; import { visit, currentURL, click, find } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; import { TYPE_TARGET_TCP, TYPE_TARGET_SSH } from 'api/models/target'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; module('Acceptance | targets | read', function (hooks) { setupApplicationTest(hooks); @@ -51,7 +47,7 @@ module('Acceptance | targets | read', function (hooks) { aliases: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { featuresService = this.owner.lookup('service:features'); // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); @@ -91,7 +87,7 @@ module('Acceptance | targets | read', function (hooks) { urls.alias = `${urls.tcpTarget}/${aliasResource.id}`; - authenticateSession({ username: 'admin' }); + await authenticateSession({ username: 'admin' }); }); test('visiting ssh target', async function (assert) { @@ -125,8 +121,10 @@ module('Acceptance | targets | read', function (hooks) { await click(`[href="${urls.targets}"]`); - assert.dom('.rose-table-body tr:first-child a').doesNotExist(); - assert.dom(`[href="${urls.tcpTarget}"]`).exists(); + assert.dom(commonSelectors.TABLE_RESOURCE_LINK(urls.tcpTarget)).isVisible(); + assert + .dom(commonSelectors.TABLE_RESOURCE_LINK(urls.sshTarget)) + .doesNotExist(); }); test('cannot navigate to a tcp target form without proper authorization', async function (assert) { @@ -137,15 +135,19 @@ module('Acceptance | targets | read', function (hooks) { await click(`[href="${urls.targets}"]`); - assert.dom('.rose-table-body tr:nth-child(2) a').doesNotExist(); - assert.dom(`[href="${urls.sshTarget}"]`).exists(); + assert.dom(commonSelectors.TABLE_RESOURCE_LINK(urls.sshTarget)).isVisible(); + assert + .dom(commonSelectors.TABLE_RESOURCE_LINK(urls.tcpTarget)) + .doesNotExist(); }); test('visiting an unknown target displays 404 message', async function (assert) { await visit(urls.unknownTarget); await a11yAudit(); - assert.dom('.rose-message-subtitle').hasText('Error 404'); + assert + .dom(commonSelectors.RESOURCE_NOT_FOUND_SUBTITLE) + .hasText(commonSelectors.RESOURCE_NOT_FOUND_VALUE); }); test('users can link to docs page for target', async function (assert) { @@ -160,7 +162,7 @@ module('Acceptance | targets | read', function (hooks) { .exists(); }); - test('users can navigate to target and incorrect url autocorrects', async function (assert) { + test('users can navigate to target and incorrect url auto-corrects', async function (assert) { const incorrectUrl = `/scopes/${instances.scopes.org.id}/targets/${instances.sshTarget.id}`; await visit(incorrectUrl); diff --git a/ui/admin/tests/acceptance/targets/update-test.js b/ui/admin/tests/acceptance/targets/update-test.js index d66dc2e19e..ca029faf7d 100644 --- a/ui/admin/tests/acceptance/targets/update-test.js +++ b/ui/admin/tests/acceptance/targets/update-test.js @@ -5,16 +5,12 @@ import { module, test } from 'qunit'; import { visit, currentURL, click, fillIn } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import { Response } from 'miragejs'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; module('Acceptance | targets | update', function (hooks) { setupApplicationTest(hooks); @@ -39,7 +35,7 @@ module('Acceptance | targets | update', function (hooks) { newTarget: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { featuresService = this.owner.lookup('service:features'); // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); @@ -62,7 +58,7 @@ module('Acceptance | targets | update', function (hooks) { urls.unknownTarget = `${urls.targets}/foo`; urls.newTarget = `${urls.targets}/new`; - authenticateSession({}); + await authenticateSession({}); }); test('can save changes to existing target', async function (assert) { @@ -140,9 +136,9 @@ module('Acceptance | targets | update', function (hooks) { await click(`[href="${urls.targets}"]`); - assert.dom('.rose-dialog').exists(); + assert.dom(commonSelectors.MODAL_WARNING).isVisible(); - await click('.rose-dialog-footer button:first-child', 'Click Discard'); + await click(commonSelectors.MODAL_WARNING_CONFIRM_BTN); assert.strictEqual(currentURL(), urls.targets); assert.notEqual(this.server.schema.targets.first().name, 'random string'); @@ -160,8 +156,8 @@ module('Acceptance | targets | update', function (hooks) { assert.strictEqual(currentURL(), urls.target); await click(`[href="${urls.targets}"]`); - assert.dom('.rose-dialog').exists(); - await click('.rose-dialog-footer button:last-child', 'Click Cancel'); + assert.dom(commonSelectors.MODAL_WARNING).isVisible(); + await click(commonSelectors.MODAL_WARNING_CANCEL_BTN); assert.strictEqual(currentURL(), urls.target); assert.notEqual(this.server.schema.targets.first().name, 'random string'); @@ -213,8 +209,8 @@ module('Acceptance | targets | update', function (hooks) { await fillIn('[name="address"]', '0.0.0.0'); await click('[type="submit"]'); - assert.dom('.rose-dialog').isVisible(); - await click('.rose-dialog-footer .rose-button-primary', 'Remove resources'); + assert.dom(commonSelectors.MODAL_WARNING).isVisible(); + await click(commonSelectors.MODAL_WARNING_CONFIRM_BTN); assert.strictEqual( this.server.schema.targets.find(target.id).address, @@ -258,8 +254,8 @@ module('Acceptance | targets | update', function (hooks) { await fillIn('[name="address"]', '0.0.0.0'); await click('[type="submit"]'); - assert.dom('.rose-dialog').isVisible(); - await click('.rose-dialog-footer .rose-button-secondary', 'Cancel'); + assert.dom(commonSelectors.MODAL_WARNING).isVisible(); + await click(commonSelectors.MODAL_WARNING_CANCEL_BTN); assert.strictEqual( this.server.schema.targets.find(target.id).address, diff --git a/ui/admin/tests/acceptance/targets/workers-test.js b/ui/admin/tests/acceptance/targets/workers-test.js index 4a73e4eaf4..d7858a45f4 100644 --- a/ui/admin/tests/acceptance/targets/workers-test.js +++ b/ui/admin/tests/acceptance/targets/workers-test.js @@ -5,18 +5,22 @@ import { module, test } from 'qunit'; import { visit, currentURL, click, fillIn } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { authenticateSession } from 'ember-simple-auth/test-support'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; module('Acceptance | targets | workers', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); + let intl; let featuresService; let featureEdition; + const ACCORDION_DROPDOWN_TEXT_SELECTOR = (name) => + `[data-test-target-${name}-workers-accordion-item] a`; const ACCORDION_DROPDOWN_SELECTOR = (name) => `[data-test-target-${name}-workers-accordion-item] .hds-accordion-item__button`; const CODE_BLOCK_SELECTOR = (name) => @@ -25,10 +29,6 @@ module('Acceptance | targets | workers', function (hooks) { '[data-test-code-editor-field-editor] textarea'; const SAVE_BUTTON_SELECTOR = '[type="submit"]'; const CANCEL_BUTTON_SELECTOR = '.rose-form-actions [type="button"]'; - const MODAL_DISCARD_BUTTON_SELECTOR = - '.rose-dialog-footer button:first-child'; - const MODAL_CANCEL_BUTTON_SELECTOR = '.rose-dialog-footer button:last-child'; - const CONFIRM_MODAL_SELECTOR = '.rose-dialog'; const instances = { scopes: { @@ -49,7 +49,8 @@ module('Acceptance | targets | workers', function (hooks) { targetEditIngressFilter: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { + intl = this.owner.lookup('service:intl'); featuresService = this.owner.lookup('service:features'); featureEdition = this.owner.lookup('service:featureEdition'); @@ -76,7 +77,7 @@ module('Acceptance | targets | workers', function (hooks) { urls.targetEditEgressFilter = `${urls.target}/edit-egress-worker-filter`; urls.targetEditIngressFilter = `${urls.target}/edit-ingress-worker-filter`; - authenticateSession({ username: 'admin' }); + await authenticateSession({ username: 'admin' }); }); test('visiting target workers', async function (assert) { @@ -218,7 +219,10 @@ module('Acceptance | targets | workers', function (hooks) { await click(CANCEL_BUTTON_SELECTOR); assert.strictEqual(currentURL(), urls.targetWorkers); - assert.notEqual(instances.target.inress_worker_filter, ingressWorkerFilter); + assert.notEqual( + instances.target.ingress_worker_filter, + ingressWorkerFilter, + ); assert .dom(CODE_BLOCK_SELECTOR('ingress')) .hasText(instances.target.ingress_worker_filter); @@ -241,6 +245,38 @@ module('Acceptance | targets | workers', function (hooks) { assert.dom(CODE_BLOCK_SELECTOR('egress')).hasText(egressWorkerFilter); }); + test('user will see "Add worker filter" if no filter set', async function (assert) { + featuresService.enable('target-worker-filters-v2-ingress'); + instances.target.update({ + egress_worker_filter: '', + ingress_worker_filter: '', + }); + await visit(urls.target); + + await click(`[href="${urls.targetWorkers}"]`); + + assert + .dom(ACCORDION_DROPDOWN_TEXT_SELECTOR('egress')) + .hasText(intl.t('actions.add-worker-filter')); + assert + .dom(ACCORDION_DROPDOWN_TEXT_SELECTOR('ingress')) + .hasText(intl.t('actions.add-worker-filter')); + }); + + test('user will see "Edit worker filter" if filter is set', async function (assert) { + featuresService.enable('target-worker-filters-v2-ingress'); + await visit(urls.target); + + await click(`[href="${urls.targetWorkers}"]`); + + assert + .dom(ACCORDION_DROPDOWN_TEXT_SELECTOR('egress')) + .hasText(intl.t('actions.edit-worker-filter')); + assert + .dom(ACCORDION_DROPDOWN_TEXT_SELECTOR('ingress')) + .hasText(intl.t('actions.edit-worker-filter')); + }); + test('user can cancel changes to egress worker filter in a target', async function (assert) { const egressWorkerFilter = '"random" in "/worker/filters"'; await visit(urls.target); @@ -273,9 +309,9 @@ module('Acceptance | targets | workers', function (hooks) { await fillIn(CODE_EDITOR_CONTENT_SELECTOR, ingressWorkerFilter); await click(`[href="${urls.target}"]`); - assert.dom(CONFIRM_MODAL_SELECTOR).isVisible(); + assert.dom(commonSelectors.MODAL_WARNING).isVisible(); - await click(MODAL_DISCARD_BUTTON_SELECTOR, 'Click Discard'); + await click(commonSelectors.MODAL_WARNING_CONFIRM_BTN, 'Click Discard'); assert.strictEqual(currentURL(), urls.target); assert.notEqual( @@ -298,9 +334,9 @@ module('Acceptance | targets | workers', function (hooks) { await fillIn(CODE_EDITOR_CONTENT_SELECTOR, ingressWorkerFilter); await click(`[href="${urls.target}"]`); - assert.dom(CONFIRM_MODAL_SELECTOR).isVisible(); + assert.dom(commonSelectors.MODAL_WARNING).isVisible(); - await click(MODAL_CANCEL_BUTTON_SELECTOR, 'Click Cancel'); + await click(commonSelectors.MODAL_WARNING_CANCEL_BTN, 'Click Cancel'); assert.strictEqual(currentURL(), urls.targetEditIngressFilter); assert.notEqual( @@ -322,9 +358,9 @@ module('Acceptance | targets | workers', function (hooks) { await fillIn(CODE_EDITOR_CONTENT_SELECTOR, egressWorkerFilter); await click(`[href="${urls.target}"]`); - assert.dom(CONFIRM_MODAL_SELECTOR).isVisible(); + assert.dom(commonSelectors.MODAL_WARNING).isVisible(); - await click(MODAL_DISCARD_BUTTON_SELECTOR, 'Click Discard'); + await click(commonSelectors.MODAL_WARNING_CONFIRM_BTN, 'Click Discard'); assert.strictEqual(currentURL(), urls.target); assert.notEqual(instances.target.egress_worker_filter, egressWorkerFilter); @@ -343,9 +379,9 @@ module('Acceptance | targets | workers', function (hooks) { await fillIn(CODE_EDITOR_CONTENT_SELECTOR, egressWorkerFilter); await click(`[href="${urls.target}"]`); - assert.dom(CONFIRM_MODAL_SELECTOR).isVisible(); + assert.dom(commonSelectors.MODAL_WARNING).isVisible(); - await click(MODAL_CANCEL_BUTTON_SELECTOR, 'Click Cancel'); + await click(commonSelectors.MODAL_WARNING_CANCEL_BTN, 'Click Cancel'); assert.strictEqual(currentURL(), urls.targetEditEgressFilter); assert.notEqual(instances.target.egress_worker_filter, egressWorkerFilter); diff --git a/ui/admin/tests/acceptance/users/accounts-test.js b/ui/admin/tests/acceptance/users/accounts-test.js index 75e1f55966..7173ab6fdb 100644 --- a/ui/admin/tests/acceptance/users/accounts-test.js +++ b/ui/admin/tests/acceptance/users/accounts-test.js @@ -5,16 +5,11 @@ import { module, test } from 'qunit'; import { visit, currentURL, click } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { Response } from 'miragejs'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; import { TYPE_AUTH_METHOD_LDAP } from 'api/models/auth-method'; module('Acceptance | users | accounts', function (hooks) { @@ -53,8 +48,8 @@ module('Acceptance | users | accounts', function (hooks) { addAccounts: null, }; - hooks.beforeEach(function () { - authenticateSession({ username: 'admin' }); + hooks.beforeEach(async function () { + await authenticateSession({ username: 'admin' }); instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', diff --git a/ui/admin/tests/acceptance/users/create-test.js b/ui/admin/tests/acceptance/users/create-test.js index 0d34306ec1..1d4b0a0e0f 100644 --- a/ui/admin/tests/acceptance/users/create-test.js +++ b/ui/admin/tests/acceptance/users/create-test.js @@ -5,16 +5,11 @@ import { module, test } from 'qunit'; import { visit, currentURL, click, fillIn } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import { Response } from 'miragejs'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; module('Acceptance | users | create', function (hooks) { setupApplicationTest(hooks); @@ -38,7 +33,7 @@ module('Acceptance | users | create', function (hooks) { newUser: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', @@ -54,7 +49,7 @@ module('Acceptance | users | create', function (hooks) { getUsersCount = () => { return this.server.schema.users.all().models.length; }; - authenticateSession({}); + await authenticateSession({}); }); test('can create new users', async function (assert) { diff --git a/ui/admin/tests/acceptance/users/delete-test.js b/ui/admin/tests/acceptance/users/delete-test.js index 24c647fb09..14d6270e18 100644 --- a/ui/admin/tests/acceptance/users/delete-test.js +++ b/ui/admin/tests/acceptance/users/delete-test.js @@ -5,16 +5,12 @@ import { module, test } from 'qunit'; import { visit, click, currentURL } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import { Response } from 'miragejs'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; module('Acceptance | users | delete', function (hooks) { setupApplicationTest(hooks); @@ -38,7 +34,7 @@ module('Acceptance | users | delete', function (hooks) { user: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', @@ -51,7 +47,7 @@ module('Acceptance | users | delete', function (hooks) { urls.users = `${urls.orgScope}/users`; urls.user = `${urls.users}/${instances.user.id}`; - authenticateSession({}); + await authenticateSession({}); }); test('can delete a user', async function (assert) { @@ -86,7 +82,7 @@ module('Acceptance | users | delete', function (hooks) { await click(`[href="${urls.user}"]`); await click(MANAGE_DROPDOWN_SELECTOR); await click(DELETE_ACTION_SELECTOR); - await click('.rose-dialog .rose-button-primary'); + await click(commonSelectors.MODAL_WARNING_CONFIRM_BTN); assert .dom('[data-test-toast-notification] .hds-alert__description') @@ -104,7 +100,7 @@ module('Acceptance | users | delete', function (hooks) { await click(`[href="${urls.user}"]`); await click(MANAGE_DROPDOWN_SELECTOR); await click(DELETE_ACTION_SELECTOR); - await click('.rose-dialog .rose-button-secondary'); + await click(commonSelectors.MODAL_WARNING_CANCEL_BTN); assert.strictEqual(this.server.db.users.length, usersCount); assert.strictEqual(currentURL(), urls.user); diff --git a/ui/admin/tests/acceptance/users/list-test.js b/ui/admin/tests/acceptance/users/list-test.js index b5f44f78f4..cc6430f1ec 100644 --- a/ui/admin/tests/acceptance/users/list-test.js +++ b/ui/admin/tests/acceptance/users/list-test.js @@ -5,15 +5,11 @@ import { module, test } from 'qunit'; import { visit, click, fillIn, waitUntil, findAll } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; module('Acceptance | users | list', function (hooks) { setupApplicationTest(hooks); @@ -40,7 +36,7 @@ module('Acceptance | users | list', function (hooks) { user2: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', @@ -57,7 +53,7 @@ module('Acceptance | users | list', function (hooks) { urls.users = `${urls.orgScope}/users`; urls.user1 = `${urls.users}/${instances.user1.id}`; urls.user2 = `${urls.users}/${instances.user2.id}`; - authenticateSession({}); + await authenticateSession({}); }); test('users can navigate to users with proper authorization', async function (assert) { @@ -115,7 +111,8 @@ module('Acceptance | users | list', function (hooks) { await click(`[href="${urls.users}"]`); - assert.dom(`.rose-table [href="${urls.user1}"]`).doesNotExist(); + assert.dom('.hds-application-state__body-text').isVisible(); + assert.dom(commonSelectors.TABLE_RESOURCE_LINK(urls.user1)).doesNotExist(); }); test('user can navigate to users tab with only list action', async function (assert) { diff --git a/ui/admin/tests/acceptance/users/read-test.js b/ui/admin/tests/acceptance/users/read-test.js index ef3ad66f91..a9dc2f009a 100644 --- a/ui/admin/tests/acceptance/users/read-test.js +++ b/ui/admin/tests/acceptance/users/read-test.js @@ -5,16 +5,12 @@ import { module, test } from 'qunit'; import { visit, currentURL, click } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; module('Acceptance | users | read', function (hooks) { setupApplicationTest(hooks); @@ -35,7 +31,7 @@ module('Acceptance | users | read', function (hooks) { user: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', @@ -47,7 +43,7 @@ module('Acceptance | users | read', function (hooks) { urls.orgScope = `/scopes/${instances.scopes.org.id}`; urls.users = `${urls.orgScope}/users`; urls.user = `${urls.users}/${instances.user.id}`; - authenticateSession({ username: 'admin' }); + await authenticateSession({ username: 'admin' }); }); test('visiting users', async function (assert) { @@ -76,7 +72,7 @@ module('Acceptance | users | read', function (hooks) { await click(`[href="${urls.users}"]`); - assert.dom(`.rose-table [href="${urls.user}"]`).doesNotExist(); + assert.dom(commonSelectors.TABLE_RESOURCE_LINK(urls.user)).doesNotExist(); }); test('users can link to docs page for users', async function (assert) { @@ -91,7 +87,7 @@ module('Acceptance | users | read', function (hooks) { .exists(); }); - test('users can navigate to user and incorrect url autocorrects', async function (assert) { + test('users can navigate to user and incorrect url auto-corrects', async function (assert) { const incorrectUrl = `/scopes/global/users/${instances.user.id}`; await visit(incorrectUrl); diff --git a/ui/admin/tests/acceptance/users/update-test.js b/ui/admin/tests/acceptance/users/update-test.js index 6fea6b3e2d..3915d8984c 100644 --- a/ui/admin/tests/acceptance/users/update-test.js +++ b/ui/admin/tests/acceptance/users/update-test.js @@ -5,16 +5,12 @@ import { module, test } from 'qunit'; import { visit, currentURL, click, fillIn } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import { Response } from 'miragejs'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; module('Acceptance | users | update', function (hooks) { setupApplicationTest(hooks); @@ -35,7 +31,7 @@ module('Acceptance | users | update', function (hooks) { user: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { type: 'org', @@ -48,7 +44,7 @@ module('Acceptance | users | update', function (hooks) { urls.users = `${urls.orgScope}/users`; urls.user = `${urls.users}/${instances.user.id}`; - authenticateSession({}); + await authenticateSession({}); }); test('can save changes to an existing user', async function (assert) { @@ -132,8 +128,8 @@ module('Acceptance | users | update', function (hooks) { await fillIn('[name="name"]', 'Unsaved user name'); assert.strictEqual(currentURL(), urls.user); await click(`[href="${urls.users}"]`); - assert.dom('.rose-dialog').exists(); - await click('.rose-dialog-footer button:first-child', 'Click Discard'); + assert.dom(commonSelectors.MODAL_WARNING).exists(); + await click(commonSelectors.MODAL_WARNING_CONFIRM_BTN, 'Click Discard'); assert.strictEqual(currentURL(), urls.users); assert.notEqual(this.server.schema.users.first().name, 'Unsaved user name'); @@ -149,8 +145,8 @@ module('Acceptance | users | update', function (hooks) { await fillIn('[name="name"]', 'Unsaved user name'); assert.strictEqual(currentURL(), urls.user); await click(`[href="${urls.users}"]`); - assert.dom('.rose-dialog').exists(); - await click('.rose-dialog-footer button:last-child', 'Click Cancel'); + assert.dom(commonSelectors.MODAL_WARNING).exists(); + await click(commonSelectors.MODAL_WARNING_CANCEL_BTN, 'Click Cancel'); assert.strictEqual(currentURL(), urls.user); assert.notEqual(this.server.schema.users.first().name, 'Unsaved user name'); diff --git a/ui/admin/tests/acceptance/workers/create-tags-test.js b/ui/admin/tests/acceptance/workers/create-tags-test.js index 49d787f215..dcd6ec21b1 100644 --- a/ui/admin/tests/acceptance/workers/create-tags-test.js +++ b/ui/admin/tests/acceptance/workers/create-tags-test.js @@ -5,10 +5,11 @@ import { module, test } from 'qunit'; import { visit, currentURL, click, fillIn } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { authenticateSession } from 'ember-simple-auth/test-support'; +import * as commonSelectors from 'admin/tests/helpers/selectors'; module('Acceptance | workers | worker | create-tags', function (hooks) { setupApplicationTest(hooks); @@ -23,11 +24,6 @@ module('Acceptance | workers | worker | create-tags', function (hooks) { const KEY_INPUT_SELECTOR = '[name="api_tags"] tr td:first-child input'; const VALUE_INPUT_SELECTOR = '[name="api_tags"] tr td:nth-child(2) input'; const ADD_INPUT_SELECTOR = '[name="api_tags"] tr td:last-child button'; - const DISCARD_CHANGES_DIALOG = '.rose-dialog'; - const DISCARD_CHANGES_DISCARD_BUTTON = - '.rose-dialog-footer .rose-button-primary'; - const DISCARD_CHANGES_CANCEL_BUTTON = - '.rose-dialog-footer .rose-button-secondary'; const instances = { scopes: { @@ -43,7 +39,7 @@ module('Acceptance | workers | worker | create-tags', function (hooks) { createTags: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.worker = this.server.create('worker', { scope: instances.scopes.global, @@ -53,7 +49,7 @@ module('Acceptance | workers | worker | create-tags', function (hooks) { urls.tags = `${urls.worker}/tags`; urls.createTags = `${urls.worker}/create-tags`; - authenticateSession({ username: 'admin' }); + await authenticateSession({ username: 'admin' }); }); test('visiting worker create tags', async function (assert) { @@ -116,9 +112,9 @@ module('Acceptance | workers | worker | create-tags', function (hooks) { await click(`[href="${urls.workers}"]`); - assert.dom(DISCARD_CHANGES_DIALOG).isVisible(); + assert.dom(commonSelectors.MODAL_WARNING).isVisible(); - await click(DISCARD_CHANGES_DISCARD_BUTTON); + await click(commonSelectors.MODAL_WARNING_CONFIRM_BTN); assert.strictEqual(currentURL(), urls.workers); }); @@ -133,9 +129,9 @@ module('Acceptance | workers | worker | create-tags', function (hooks) { await click(`[href="${urls.workers}"]`); - assert.dom(DISCARD_CHANGES_DIALOG).isVisible(); + assert.dom(commonSelectors.MODAL_WARNING).isVisible(); - await click(DISCARD_CHANGES_CANCEL_BUTTON); + await click(commonSelectors.MODAL_WARNING_CANCEL_BTN); assert.strictEqual(currentURL(), urls.createTags); }); diff --git a/ui/admin/tests/acceptance/workers/create-test.js b/ui/admin/tests/acceptance/workers/create-test.js index 365d5f4d90..be006339d7 100644 --- a/ui/admin/tests/acceptance/workers/create-test.js +++ b/ui/admin/tests/acceptance/workers/create-test.js @@ -5,15 +5,10 @@ import { module, test } from 'qunit'; import { visit, fillIn, click, findAll, currentURL } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { Response } from 'miragejs'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; module('Acceptance | workers | create', function (hooks) { setupApplicationTest(hooks); @@ -24,14 +19,14 @@ module('Acceptance | workers | create', function (hooks) { let newWorkerURL; let getWorkersCount; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { globalScope = this.server.create('scope', { id: 'global' }); workersURL = `/scopes/global/workers`; newWorkerURL = `${workersURL}/new`; getWorkersCount = () => this.server.schema.workers.all().length; - authenticateSession({}); + await authenticateSession({}); }); test('can create new workers', async function (assert) { diff --git a/ui/admin/tests/acceptance/workers/delete-test.js b/ui/admin/tests/acceptance/workers/delete-test.js index c040c0f411..73c0b94f69 100644 --- a/ui/admin/tests/acceptance/workers/delete-test.js +++ b/ui/admin/tests/acceptance/workers/delete-test.js @@ -5,17 +5,12 @@ import { module, test } from 'qunit'; import { visit, click } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { Response } from 'miragejs'; import { resolve, reject } from 'rsvp'; import sinon from 'sinon'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; module('Acceptance | workers | delete', function (hooks) { setupApplicationTest(hooks); @@ -39,7 +34,7 @@ module('Acceptance | workers | delete', function (hooks) { worker: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.worker = this.server.create('worker', { @@ -52,7 +47,7 @@ module('Acceptance | workers | delete', function (hooks) { getWorkerCount = () => this.server.schema.workers.all().models.length; - authenticateSession({}); + await authenticateSession({}); }); test('can delete a worker', async function (assert) { diff --git a/ui/admin/tests/acceptance/workers/list-test.js b/ui/admin/tests/acceptance/workers/list-test.js index 092a0d4410..960bd3bf79 100644 --- a/ui/admin/tests/acceptance/workers/list-test.js +++ b/ui/admin/tests/acceptance/workers/list-test.js @@ -5,15 +5,10 @@ import { module, test } from 'qunit'; import { visit, click, currentURL } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; const WORKERS_FLYOUT = '[data-test-worker-tags-flyout]'; const WORKERS_FLYOUT_DISMISS = '[data-test-worker-tags-flyout] div button'; @@ -49,7 +44,7 @@ module('Acceptance | workers | list', function (hooks) { workerTags: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.worker = this.server.create('worker', { scope: instances.scopes.global, @@ -61,7 +56,7 @@ module('Acceptance | workers | list', function (hooks) { urls.workers = `/scopes/global/workers`; urls.worker = `${urls.workers}/${instances.worker.id}`; urls.workerTags = `${urls.worker}/tags`; - authenticateSession({}); + await authenticateSession({}); featuresService = this.owner.lookup('service:features'); }); diff --git a/ui/admin/tests/acceptance/workers/read-test.js b/ui/admin/tests/acceptance/workers/read-test.js index a49566639c..f76dbdcb61 100644 --- a/ui/admin/tests/acceptance/workers/read-test.js +++ b/ui/admin/tests/acceptance/workers/read-test.js @@ -5,16 +5,11 @@ import { module, test } from 'qunit'; import { visit, currentURL, click } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; module('Acceptance | workers | read', function (hooks) { setupApplicationTest(hooks); @@ -37,7 +32,7 @@ module('Acceptance | workers | read', function (hooks) { worker: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { //Generate the resources instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { @@ -54,7 +49,7 @@ module('Acceptance | workers | read', function (hooks) { featuresService = this.owner.lookup('service:features'); featuresService.enable('byow'); - authenticateSession({ username: 'admin' }); + await authenticateSession({ username: 'admin' }); }); test('visiting worker', async function (assert) { diff --git a/ui/admin/tests/acceptance/workers/tags-test.js b/ui/admin/tests/acceptance/workers/tags-test.js index 06b7170d08..fdd4653bb9 100644 --- a/ui/admin/tests/acceptance/workers/tags-test.js +++ b/ui/admin/tests/acceptance/workers/tags-test.js @@ -12,7 +12,7 @@ import { fillIn, find, } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { authenticateSession } from 'ember-simple-auth/test-support'; @@ -64,7 +64,7 @@ module('Acceptance | workers | worker | tags', function (hooks) { createTags: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.worker = this.server.create('worker', { scope: instances.scopes.global, @@ -73,7 +73,7 @@ module('Acceptance | workers | worker | tags', function (hooks) { urls.worker = `${urls.workers}/${instances.worker.id}`; urls.tags = `${urls.worker}/tags`; urls.createTags = `${urls.worker}/create-tags`; - authenticateSession({ username: 'admin' }); + await authenticateSession({ username: 'admin' }); }); test('visiting worker tags', async function (assert) { diff --git a/ui/admin/tests/acceptance/workers/update-test.js b/ui/admin/tests/acceptance/workers/update-test.js index 23bbe435e0..26b40e2d13 100644 --- a/ui/admin/tests/acceptance/workers/update-test.js +++ b/ui/admin/tests/acceptance/workers/update-test.js @@ -5,15 +5,10 @@ import { module, test } from 'qunit'; import { visit, click, find, fillIn } from '@ember/test-helpers'; -import { setupApplicationTest } from 'ember-qunit'; +import { setupApplicationTest } from 'admin/tests/helpers'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { Response } from 'miragejs'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; +import { authenticateSession } from 'ember-simple-auth/test-support'; module('Acceptance | workers | update', function (hooks) { setupApplicationTest(hooks); @@ -31,7 +26,7 @@ module('Acceptance | workers | update', function (hooks) { worker: null, }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { //Generate the resources instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.worker = this.server.create('worker', { @@ -41,7 +36,7 @@ module('Acceptance | workers | update', function (hooks) { urls.globalScope = '/scopes/global'; urls.workers = `${urls.globalScope}/workers`; (urls.worker = `${urls.workers}/${instances.worker.id}`), - authenticateSession({}); + await authenticateSession({}); }); test('can save changes to an existing worker', async function (assert) { diff --git a/ui/admin/tests/helpers/index.js b/ui/admin/tests/helpers/index.js index 371167e6c5..be3d6bd1fe 100644 --- a/ui/admin/tests/helpers/index.js +++ b/ui/admin/tests/helpers/index.js @@ -8,6 +8,10 @@ import { setupRenderingTest as upstreamSetupRenderingTest, setupTest as upstreamSetupTest, } from 'ember-qunit'; +import { + currentSession, + authenticateSession, +} from 'ember-simple-auth/test-support'; // This file exists to provide wrappers around ember-qunit's / ember-mocha's // test setup functions. This way, you can easily extend the setup that is @@ -21,9 +25,10 @@ function setupApplicationTest(hooks, options) { // For example, if you need an authenticated session for each // application test, you could do: // - // hooks.beforeEach(async function () { - // await authenticateSession(); // ember-simple-auth - // }); + hooks.beforeEach(async function () { + await authenticateSession(); + currentSession().set('data.theme', 'light'); + }); // // This is also a good place to call test setup functions coming // from other addons: diff --git a/ui/admin/tests/helpers/selectors.js b/ui/admin/tests/helpers/selectors.js index 65423c6bf2..ab04484892 100644 --- a/ui/admin/tests/helpers/selectors.js +++ b/ui/admin/tests/helpers/selectors.js @@ -14,13 +14,16 @@ export const FIELD_NAME_ERROR = '[data-test-error-message-name]'; export const FIELD_DESCRIPTION = '[name=description]'; export const FIELD_DESCRIPTION_VALUE = 'description'; -export const TABLE_FIRST_ROW_RESOURCE_LINK = 'tbody tr:first-child a'; +export const TABLE_RESOURCE_LINK = (url) => `tbody [href="${url}"]`; -export const RESOURCE_NOT_FOUND_SUBTITLE = '.rose-message-subtitle'; -export const RESOURCE_NOT_FOUND_VALUE = 'Error 404'; +export const RESOURCE_NOT_FOUND_SUBTITLE = + '[data-test-error-application-state] .hds-application-state__error-code'; +export const RESOURCE_NOT_FOUND_VALUE = 'ERROR 404'; -export const PAGE_MESSAGE_DESCRIPTION = '.rose-message-description'; -export const PAGE_MESSAGE_LINK = '.rose-message-body .hds-link-standalone'; +export const PAGE_MESSAGE_HEADER = '.hds-application-state__header'; +export const PAGE_MESSAGE_DESCRIPTION = '.hds-application-state__body-text'; +export const PAGE_MESSAGE_LINK = + '.hds-application-state__footer .hds-link-standalone'; export const HREF = (url) => `[href="${url}"]`; @@ -31,3 +34,17 @@ export const ALERT_TOAST_BODY = '[data-test-toast-notification] .hds-alert__description'; export const ALERT_TOAST_DISMISS = '[data-test-toast-notification] .hds-dismiss-button'; + +export const MODAL_WARNING = 'dialog'; +export const MODAL_WARNING_CONFIRM_BTN = + 'dialog .hds-modal__footer button:first-child'; +export const MODAL_WARNING_CANCEL_BTN = + 'dialog .hds-modal__footer button:last-child'; +export const MODAL_WARNING_TITLE = '.hds-modal__header'; +export const MODAL_WARNING_MESSAGE = '.hds-modal__body'; + +export const FILTER_DROPDOWN = (filterName) => + `.search-filtering [name="${filterName}"] button`; +export const FILTER_DROPDOWN_ITEM = (itemList) => `input[value="${itemList}"]`; +export const FILTER_DROPDOWN_ITEM_APPLY_BTN = (filterName) => + `.search-filtering [name="${filterName}"] .hds-dropdown__footer button`; diff --git a/ui/admin/tests/integration/components/form/worker/create-worker-led-test.js b/ui/admin/tests/integration/components/form/worker/create-worker-led-test.js index 986b604a89..fdaca3b83c 100644 --- a/ui/admin/tests/integration/components/form/worker/create-worker-led-test.js +++ b/ui/admin/tests/integration/components/form/worker/create-worker-led-test.js @@ -7,7 +7,7 @@ import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; import { render } from '@ember/test-helpers'; import { hbs } from 'ember-cli-htmlbars'; -import { v1 } from 'ember-uuid'; +import { v4 as uuidv4 } from 'uuid'; import { setupIntl } from 'ember-intl/test-support'; import { setupBrowserFakes } from 'ember-browser-services/test-support'; @@ -25,7 +25,7 @@ module( test('it correctly populates the cluster id for an hcp dev cluster', async function (assert) { const windowService = this.owner.lookup('service:browser/window'); - const guid = v1(); + const guid = uuidv4(); this.model = {}; this.submit = () => {}; this.cancel = () => {}; @@ -41,7 +41,7 @@ module( test('it correctly populates the cluster id for an hcp int cluster', async function (assert) { const windowService = this.owner.lookup('service:browser/window'); - const guid = v1(); + const guid = uuidv4(); this.model = {}; this.submit = () => {}; this.cancel = () => {}; @@ -57,7 +57,7 @@ module( test('it correctly populates the cluster id for an hcp prod cluster', async function (assert) { const windowService = this.owner.lookup('service:browser/window'); - const guid = v1(); + const guid = uuidv4(); this.model = {}; this.submit = () => {}; this.cancel = () => {}; diff --git a/ui/admin/tests/integration/components/worker-filter-generator/index-test.js b/ui/admin/tests/integration/components/worker-filter-generator/index-test.js new file mode 100644 index 0000000000..c09731ba0d --- /dev/null +++ b/ui/admin/tests/integration/components/worker-filter-generator/index-test.js @@ -0,0 +1,146 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render, click, fillIn, select } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; +import { setupIntl } from 'ember-intl/test-support'; + +module( + 'Integration | Component | worker-filter-generator/index', + function (hooks) { + setupRenderingTest(hooks); + setupIntl(hooks, 'en-us'); + + const CODE_EDITOR = '[data-test-code-editor-field-editor]'; + const CODE_EDITOR_LINE = + '[data-test-code-editor-field-editor] .CodeMirror-line'; + const FILTER_GENERATOR = '[name="filter_generator"]'; + const SHOW_FILTER_GENERATOR = '[name="show_filter_generator"]'; + const TAG_TYPE_OPTION = '[value="tag"]'; + const NAME_TYPE_OPTION = '[value="name"]'; + const TAG_KEY = '[name="tag_key"]'; + const TAG_VALUE = '[name="tag_value"]'; + const NAME_OPERATOR = '[name="name_operator"]'; + const GENERATED_VALUE = '[name="generated_value"]'; + + test('it renders correct content when egress_worker_filter is passed in', async function (assert) { + this.model = { egress_worker_filter: 'egress filter' }; + await render( + hbs``, + ); + + assert.dom(CODE_EDITOR).isVisible(); + assert.dom(CODE_EDITOR_LINE).hasText(this.model.egress_worker_filter); + }); + + test('it renders correct content when ingress_worker_filter is passed in', async function (assert) { + this.model = { ingress_worker_filter: 'ingress filter' }; + await render( + hbs``, + ); + + assert.dom(CODE_EDITOR).isVisible(); + assert.dom(CODE_EDITOR_LINE).hasText(this.model.ingress_worker_filter); + }); + + test('toggleFilterGenerator shows filter generator when toggled on', async function (assert) { + this.model = { ingress_worker_filter: 'ingress filter' }; + await render( + hbs``, + ); + + assert.dom(FILTER_GENERATOR).isVisible(); + + await click(SHOW_FILTER_GENERATOR); + + assert.dom(FILTER_GENERATOR).isNotVisible(); + + await click(SHOW_FILTER_GENERATOR); + + assert.dom(FILTER_GENERATOR).isVisible(); + }); + + test('filter generator tag type shows key and value input boxes', async function (assert) { + this.model = { ingress_worker_filter: 'ingress filter' }; + await render( + hbs``, + ); + + assert.dom(FILTER_GENERATOR).isVisible(); + + await click(TAG_TYPE_OPTION); + + assert.dom(TAG_KEY).isVisible(); + assert.dom(TAG_VALUE).isVisible(); + assert.dom(NAME_OPERATOR).isNotVisible(); + }); + + test('filter generator tag type generates correctly formatted filter', async function (assert) { + this.model = { ingress_worker_filter: 'ingress filter' }; + await render( + hbs``, + ); + + assert.dom(FILTER_GENERATOR).isVisible(); + + await click(TAG_TYPE_OPTION); + await fillIn(TAG_KEY, 'key1'); + await fillIn(TAG_VALUE, 'val1'); + + assert.dom(GENERATED_VALUE).hasValue('"val1" in "/tags/key1"'); + }); + + test('filter generator name type shows operator and value fields', async function (assert) { + this.model = { ingress_worker_filter: 'ingress filter' }; + await render( + hbs``, + ); + + assert.dom(FILTER_GENERATOR).isVisible(); + + await click(NAME_TYPE_OPTION); + + assert.dom(NAME_OPERATOR).isVisible(); + assert.dom(TAG_VALUE).isVisible(); + assert.dom(TAG_KEY).isNotVisible(); + }); + + test('filter generator name type generates correctly formatted filter', async function (assert) { + this.model = { ingress_worker_filter: 'ingress filter' }; + await render( + hbs``, + ); + + assert.dom(FILTER_GENERATOR).isVisible(); + + await click(NAME_TYPE_OPTION); + await fillIn(TAG_VALUE, 'val1'); + await select(NAME_OPERATOR, '=='); + + assert.dom(GENERATED_VALUE).hasValue('"/name" == "val1"'); + }); + + test('generated result is cleared when switching filter types', async function (assert) { + this.model = { ingress_worker_filter: 'ingress filter' }; + await render( + hbs``, + ); + + assert.dom(FILTER_GENERATOR).isVisible(); + + await click(NAME_TYPE_OPTION); + await fillIn(TAG_VALUE, 'val1'); + await select(NAME_OPERATOR, '=='); + + assert.dom(GENERATED_VALUE).hasValue('"/name" == "val1"'); + + await click(TAG_TYPE_OPTION); + + assert.dom(GENERATED_VALUE).hasNoValue(); + }); + }, +); diff --git a/ui/admin/tests/integration/components/worker-filter/index-test.js b/ui/admin/tests/integration/components/worker-filter/index-test.js deleted file mode 100644 index be095b2135..0000000000 --- a/ui/admin/tests/integration/components/worker-filter/index-test.js +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -import { module, test } from 'qunit'; -import { setupRenderingTest } from 'ember-qunit'; -import { render } from '@ember/test-helpers'; -import { hbs } from 'ember-cli-htmlbars'; -import { setupIntl } from 'ember-intl/test-support'; - -module('Integration | Component | worker-filter/index', function (hooks) { - setupRenderingTest(hooks); - setupIntl(hooks, 'en-us'); - - test('it renders correct content when egress_worker_filter is passed in', async function (assert) { - this.onInput = () => {}; - this.submit = () => {}; - this.cancel = () => {}; - this.model = { egress_worker_filter: 'egress filter' }; - await render( - hbs``, - ); - - assert.dom('[data-test-code-editor-field-editor]').isVisible(); - assert - .dom('[data-test-code-editor-field-editor] .CodeMirror-line') - .hasText(this.model.egress_worker_filter); - }); - - test('it renders correct content when ingress_worker_filter is passed in', async function (assert) { - this.onInput = () => {}; - this.submit = () => {}; - this.cancel = () => {}; - this.model = { ingress_worker_filter: 'ingress filter' }; - await render( - hbs``, - ); - - assert.dom('[data-test-code-editor-field-editor]').isVisible(); - assert - .dom('[data-test-code-editor-field-editor] .CodeMirror-line') - .hasText(this.model.ingress_worker_filter); - }); -}); diff --git a/ui/admin/tests/unit/controllers/account/change-password-test.js b/ui/admin/tests/unit/controllers/account/change-password-test.js index facd93f5d2..25a3ec5674 100644 --- a/ui/admin/tests/unit/controllers/account/change-password-test.js +++ b/ui/admin/tests/unit/controllers/account/change-password-test.js @@ -30,7 +30,7 @@ module('Unit | Controller | account/change-password', function (hooks) { }; hooks.beforeEach(async function () { - authenticateSession({}); + await authenticateSession({}); controller = this.owner.lookup('controller:account/change-password'); store = this.owner.lookup('service:store'); diff --git a/ui/admin/tests/unit/controllers/application-test.js b/ui/admin/tests/unit/controllers/application-test.js index 705d584066..5b09d92bc5 100644 --- a/ui/admin/tests/unit/controllers/application-test.js +++ b/ui/admin/tests/unit/controllers/application-test.js @@ -16,7 +16,7 @@ module('Unit | Controller | application', function (hooks) { let session; hooks.beforeEach(async function () { - authenticateSession({}); + await authenticateSession({}); controller = this.owner.lookup('controller:application'); featureEdition = this.owner.lookup('service:featureEdition'); featuresService = this.owner.lookup('service:features'); diff --git a/ui/admin/tests/unit/controllers/onboarding/index-test.js b/ui/admin/tests/unit/controllers/onboarding/index-test.js index 39017aaed4..111ad076d4 100644 --- a/ui/admin/tests/unit/controllers/onboarding/index-test.js +++ b/ui/admin/tests/unit/controllers/onboarding/index-test.js @@ -36,7 +36,7 @@ module('Unit | Controller | onboarding/index', function (hooks) { }; hooks.beforeEach(async function () { - authenticateSession({}); + await authenticateSession({}); store = this.owner.lookup('service:store'); controller = this.owner.lookup('controller:onboarding/index'); diff --git a/ui/admin/tests/unit/controllers/onboarding/success-test.js b/ui/admin/tests/unit/controllers/onboarding/success-test.js index 31df549e40..b27307222b 100644 --- a/ui/admin/tests/unit/controllers/onboarding/success-test.js +++ b/ui/admin/tests/unit/controllers/onboarding/success-test.js @@ -33,7 +33,7 @@ module('Unit | Controller | onboarding/success', function (hooks) { }; hooks.beforeEach(async function () { - authenticateSession({}); + await authenticateSession({}); controller = this.owner.lookup('controller:onboarding/success'); store = this.owner.lookup('service:store'); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/add-storage-policy/create-test.js b/ui/admin/tests/unit/controllers/scopes/scope/add-storage-policy/create-test.js index 1eea4b62dd..a141f6bd6e 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/add-storage-policy/create-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/add-storage-policy/create-test.js @@ -31,7 +31,7 @@ module( }; hooks.beforeEach(async function () { - authenticateSession({}); + await authenticateSession({}); store = this.owner.lookup('service:store'); controller = this.owner.lookup( 'controller:scopes/scope/add-storage-policy/create', diff --git a/ui/admin/tests/unit/controllers/scopes/scope/add-storage-policy/index-test.js b/ui/admin/tests/unit/controllers/scopes/scope/add-storage-policy/index-test.js index 5d90f91d8e..4e47c19c0f 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/add-storage-policy/index-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/add-storage-policy/index-test.js @@ -31,7 +31,7 @@ module( }; hooks.beforeEach(async function () { - authenticateSession({}); + await authenticateSession({}); store = this.owner.lookup('service:store'); controller = this.owner.lookup( 'controller:scopes/scope/add-storage-policy/index', diff --git a/ui/admin/tests/unit/controllers/scopes/scope/aliases/index-test.js b/ui/admin/tests/unit/controllers/scopes/scope/aliases/index-test.js index 983776c50d..2bda6b3498 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/aliases/index-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/aliases/index-test.js @@ -17,6 +17,7 @@ module('Unit | Controller | scopes/scope/aliases/index', function (hooks) { setupIndexedDb(hooks); setupIntl(hooks, 'en-us'); + let intl; let store; let controller; let getAliasCount; @@ -33,8 +34,9 @@ module('Unit | Controller | scopes/scope/aliases/index', function (hooks) { aliases: null, }; - hooks.beforeEach(function () { - authenticateSession({}); + hooks.beforeEach(async function () { + await authenticateSession({}); + intl = this.owner.lookup('service:intl'); store = this.owner.lookup('service:store'); controller = this.owner.lookup('controller:scopes/scope/aliases/index'); @@ -103,4 +105,37 @@ module('Unit | Controller | scopes/scope/aliases/index', function (hooks) { assert.notOk(alias.destination_id); }); + + test('messageDescription returns correct translation with list authorization', async function (assert) { + await visit(urls.aliases); + + assert.strictEqual( + controller.messageDescription, + intl.t('resources.alias.messages.none.description'), + ); + }); + + test('messageDescription returns correct translation with create authorization', async function (assert) { + instances.scopes.global.authorized_collection_actions.aliases = ['create']; + await visit(urls.aliases); + + assert.strictEqual( + controller.messageDescription, + intl.t('descriptions.create-but-not-list', { + resource: intl.t('resources.alias.title_plural'), + }), + ); + }); + + test('messageDescription returns correct translation with no authorization', async function (assert) { + instances.scopes.global.authorized_collection_actions.aliases = []; + await visit(urls.aliases); + + assert.strictEqual( + controller.messageDescription, + intl.t('descriptions.neither-list-nor-create', { + resource: intl.t('resources.alias.title_plural'), + }), + ); + }); }); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/auth-methods/auth-method/accounts/account/set-password-test.js b/ui/admin/tests/unit/controllers/scopes/scope/auth-methods/auth-method/accounts/account/set-password-test.js index 158f2c6a07..1439db9dbb 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/auth-methods/auth-method/accounts/account/set-password-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/auth-methods/auth-method/accounts/account/set-password-test.js @@ -33,7 +33,7 @@ module( }; hooks.beforeEach(async function () { - authenticateSession({}); + await authenticateSession({}); controller = this.owner.lookup( 'controller:scopes/scope/auth-methods/auth-method/accounts/account/set-password', ); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/auth-methods/auth-method/accounts/index-test.js b/ui/admin/tests/unit/controllers/scopes/scope/auth-methods/auth-method/accounts/index-test.js index c70fbe3b83..0154577083 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/auth-methods/auth-method/accounts/index-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/auth-methods/auth-method/accounts/index-test.js @@ -18,6 +18,7 @@ module( setupMirage(hooks); setupIntl(hooks, 'en-us'); + let intl; let controller; let store; let getAccountCount; @@ -35,7 +36,8 @@ module( }; hooks.beforeEach(async function () { - authenticateSession({}); + await authenticateSession({}); + intl = this.owner.lookup('service:intl'); controller = this.owner.lookup( 'controller:scopes/scope/auth-methods/auth-method/accounts/index', ); @@ -94,5 +96,38 @@ module( assert.strictEqual(getAccountCount(), accountCount - 1); }); + + test('messageDescription returns correct translation with list authorization', async function (assert) { + await visit(urls.accounts); + + assert.strictEqual( + controller.messageDescription, + intl.t('resources.account.description'), + ); + }); + + test('messageDescription returns correct translation with create authorization', async function (assert) { + instances.authMethod.authorized_collection_actions.accounts = ['create']; + await visit(urls.accounts); + + assert.strictEqual( + controller.messageDescription, + intl.t('descriptions.create-but-not-list', { + resource: intl.t('resources.account.title_plural'), + }), + ); + }); + + test('messageDescription returns correct translation with no authorization', async function (assert) { + instances.authMethod.authorized_collection_actions.accounts = []; + await visit(urls.accounts); + + assert.strictEqual( + controller.messageDescription, + intl.t('descriptions.neither-list-nor-create', { + resource: intl.t('resources.account.title_plural'), + }), + ); + }); }, ); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/auth-methods/auth-method/managed-groups/index-test.js b/ui/admin/tests/unit/controllers/scopes/scope/auth-methods/auth-method/managed-groups/index-test.js index e8d3171737..aaa61b2dbd 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/auth-methods/auth-method/managed-groups/index-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/auth-methods/auth-method/managed-groups/index-test.js @@ -18,6 +18,7 @@ module( setupMirage(hooks); setupIntl(hooks, 'en-us'); + let intl; let controller; let store; let getManagedGroupCount; @@ -35,7 +36,8 @@ module( }; hooks.beforeEach(async function () { - authenticateSession({}); + await authenticateSession({}); + intl = this.owner.lookup('service:intl'); controller = this.owner.lookup( 'controller:scopes/scope/auth-methods/auth-method/managed-groups/index', ); @@ -117,5 +119,40 @@ module( assert.true(managedGroup.hasDirtyAttributes); }); + + test('messageDescription returns correct translation with list authorization', async function (assert) { + await visit(urls.managedGroups); + + assert.strictEqual( + controller.messageDescription, + intl.t('resources.managed-group.description'), + ); + }); + + test('messageDescription returns correct translation with create authorization', async function (assert) { + instances.authMethod.authorized_collection_actions['managed-groups'] = [ + 'create', + ]; + await visit(urls.managedGroups); + + assert.strictEqual( + controller.messageDescription, + intl.t('descriptions.create-but-not-list', { + resource: intl.t('resources.managed-group.title_plural'), + }), + ); + }); + + test('messageDescription returns correct translation with no authorization', async function (assert) { + instances.authMethod.authorized_collection_actions['managed-groups'] = []; + await visit(urls.managedGroups); + + assert.strictEqual( + controller.messageDescription, + intl.t('descriptions.neither-list-nor-create', { + resource: intl.t('resources.managed-group.title_plural'), + }), + ); + }); }, ); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/auth-methods/index-test.js b/ui/admin/tests/unit/controllers/scopes/scope/auth-methods/index-test.js index 19bcd7534b..cb8cd49551 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/auth-methods/index-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/auth-methods/index-test.js @@ -23,6 +23,7 @@ module('Unit | Controller | scopes/scope/auth-methods/index', function (hooks) { setupIndexedDb(hooks); setupIntl(hooks, 'en-us'); + let intl; let store; let controller; let getAuthMethodCount; @@ -34,8 +35,13 @@ module('Unit | Controller | scopes/scope/auth-methods/index', function (hooks) { authMethod: null, }; + const urls = { + authMethods: null, + }; + hooks.beforeEach(async function () { - authenticateSession({}); + await authenticateSession({}); + intl = this.owner.lookup('service:intl'); store = this.owner.lookup('service:store'); controller = this.owner.lookup( 'controller:scopes/scope/auth-methods/index', @@ -46,6 +52,8 @@ module('Unit | Controller | scopes/scope/auth-methods/index', function (hooks) { scope: instances.scope.global, }); + urls.authMethods = '/scopes/global/auth-methods'; + getAuthMethodCount = () => this.server.schema.authMethods.all().models.length; }); @@ -92,7 +100,7 @@ module('Unit | Controller | scopes/scope/auth-methods/index', function (hooks) { }); test('cancel action rolls-back changes on the specified model', async function (assert) { - await visit('/scopes/global/auth-methods'); + await visit(urls.authMethods); const authMethod = await store.findRecord( 'auth-method', instances.authMethod.id, @@ -107,7 +115,7 @@ module('Unit | Controller | scopes/scope/auth-methods/index', function (hooks) { }); test('save action saves changes on the specified model', async function (assert) { - await visit('/scopes/global/auth-methods'); + await visit(urls.authMethods); const authMethod = await store.findRecord( 'auth-method', instances.authMethod.id, @@ -133,7 +141,7 @@ module('Unit | Controller | scopes/scope/auth-methods/index', function (hooks) { }); test('makePrimary action updates scope with auth-method id', async function (assert) { - await visit('/scopes/global/auth-methods'); + await visit(urls.authMethods); const authMethod = await store.findRecord( 'auth-method', instances.authMethod.id, @@ -146,7 +154,7 @@ module('Unit | Controller | scopes/scope/auth-methods/index', function (hooks) { }); test('removeAsPrimary action updates scope with null as auth-method id', async function (assert) { - await visit('/scopes/global/auth-methods'); + await visit(urls.authMethods); const authMethod = await store.findRecord( 'auth-method', instances.authMethod.id, @@ -230,4 +238,39 @@ module('Unit | Controller | scopes/scope/auth-methods/index', function (hooks) { assert.strictEqual(authMethod.state, 'public'); }); + + test('messageDescription returns correct translation with list authorization', async function (assert) { + await visit(urls.authMethods); + + assert.strictEqual( + controller.messageDescription, + intl.t('resources.auth-method.description'), + ); + }); + + test('messageDescription returns correct translation with create authorization', async function (assert) { + instances.scope.global.authorized_collection_actions['auth-methods'] = [ + 'create', + ]; + await visit(urls.authMethods); + + assert.strictEqual( + controller.messageDescription, + intl.t('descriptions.create-but-not-list', { + resource: intl.t('resources.auth-method.title_plural'), + }), + ); + }); + + test('messageDescription returns correct translation with no authorization', async function (assert) { + instances.scope.global.authorized_collection_actions['auth-methods'] = []; + await visit(urls.authMethods); + + assert.strictEqual( + controller.messageDescription, + intl.t('descriptions.neither-list-nor-create', { + resource: intl.t('resources.auth-method.title_plural'), + }), + ); + }); }); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/authenticate/method/index-test.js b/ui/admin/tests/unit/controllers/scopes/scope/authenticate/method/index-test.js index 6a9227f4a8..d2b849aa1e 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/authenticate/method/index-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/authenticate/method/index-test.js @@ -35,7 +35,7 @@ module( }; hooks.beforeEach(async function () { - authenticateSession({}); + await authenticateSession({}); controller = this.owner.lookup( 'controller:scopes/scope/authenticate/method/index', ); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/credential-stores/credential-store/credential-libraries/index-test.js b/ui/admin/tests/unit/controllers/scopes/scope/credential-stores/credential-store/credential-libraries/index-test.js index 03561476a5..55996ed0c5 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/credential-stores/credential-store/credential-libraries/index-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/credential-stores/credential-store/credential-libraries/index-test.js @@ -10,6 +10,7 @@ import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIntl } from 'ember-intl/test-support'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import { authenticateSession } from 'ember-simple-auth/test-support'; +import { TYPE_CREDENTIAL_STORE_VAULT } from 'api/models/credential-store'; module( 'Unit | Controller | scopes/scope/credential-stores/credential-store/credential-libraries/index', @@ -19,6 +20,7 @@ module( setupIndexedDb(hooks); setupIntl(hooks, 'en-us'); + let intl; let store; let controller; let getCredentialLibraryCount; @@ -37,8 +39,9 @@ module( credentialLibraries: null, }; - hooks.beforeEach(function () { - authenticateSession({}); + hooks.beforeEach(async function () { + await authenticateSession({}); + intl = intl = this.owner.lookup('service:intl'); store = this.owner.lookup('service:store'); controller = this.owner.lookup( 'controller:scopes/scope/credential-stores/credential-store/credential-libraries/index', @@ -54,6 +57,7 @@ module( scope: { id: instances.scopes.org.id, type: 'org' }, }); instances.credentialStore = this.server.create('credential-store', { + type: TYPE_CREDENTIAL_STORE_VAULT, scope: instances.scopes.project, }); instances.credentialLibrary = this.server.create('credential-library', { @@ -127,5 +131,42 @@ module( assert.true(credentialLibrary.hasDirtyAttributes); }); + + test('messageDescription returns correct translation with list authorization', async function (assert) { + await visit(urls.credentialLibraries); + + assert.strictEqual( + controller.messageDescription, + intl.t('resources.credential-library.description'), + ); + }); + + test('messageDescription returns correct translation with create authorization', async function (assert) { + instances.credentialStore.authorized_collection_actions[ + 'credential-libraries' + ] = ['create']; + await visit(urls.credentialLibraries); + + assert.strictEqual( + controller.messageDescription, + intl.t('descriptions.create-but-not-list', { + resource: intl.t('resources.credential-library.title_plural'), + }), + ); + }); + + test('messageDescription returns correct translation with no authorization', async function (assert) { + instances.credentialStore.authorized_collection_actions[ + 'credential-libraries' + ] = []; + await visit(urls.credentialLibraries); + + assert.strictEqual( + controller.messageDescription, + intl.t('descriptions.neither-list-nor-create', { + resource: intl.t('resources.credential-library.title_plural'), + }), + ); + }); }, ); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/credential-stores/credential-store/credentials/index-test.js b/ui/admin/tests/unit/controllers/scopes/scope/credential-stores/credential-store/credentials/index-test.js index a4f326e1df..fca13fc6dc 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/credential-stores/credential-store/credentials/index-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/credential-stores/credential-store/credentials/index-test.js @@ -10,6 +10,7 @@ import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupIntl } from 'ember-intl/test-support'; import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; import { authenticateSession } from 'ember-simple-auth/test-support'; +import { TYPE_CREDENTIAL_STORE_STATIC } from 'api/models/credential-store'; module( 'Unit | Controller | scopes/scope/credential-stores/credential-store/credentials/index', @@ -19,6 +20,7 @@ module( setupIndexedDb(hooks); setupIntl(hooks, 'en-us'); + let intl; let store; let controller; let getCredentialCount; @@ -37,8 +39,9 @@ module( credentials: null, }; - hooks.beforeEach(function () { - authenticateSession({}); + hooks.beforeEach(async function () { + await authenticateSession({}); + intl = this.owner.lookup('service:intl'); store = this.owner.lookup('service:store'); controller = this.owner.lookup( 'controller:scopes/scope/credential-stores/credential-store/credentials/index', @@ -54,6 +57,7 @@ module( scope: { id: instances.scopes.org.id, type: 'org' }, }); instances.credentialStore = this.server.create('credential-store', { + type: TYPE_CREDENTIAL_STORE_STATIC, scope: instances.scopes.project, }); instances.credential = this.server.create('credential', { @@ -111,5 +115,40 @@ module( assert.strictEqual(getCredentialCount(), credentialCount - 1); }); + + test('messageDescription returns correct translation with list authorization', async function (assert) { + await visit(urls.credentials); + + assert.strictEqual( + controller.messageDescription, + intl.t('resources.credential.description'), + ); + }); + + test('messageDescription returns correct translation with create authorization', async function (assert) { + instances.credentialStore.authorized_collection_actions.credentials = [ + 'create', + ]; + await visit(urls.credentials); + + assert.strictEqual( + controller.messageDescription, + intl.t('descriptions.create-but-not-list', { + resource: intl.t('resources.credential.title_plural'), + }), + ); + }); + + test('messageDescription returns correct translation with no authorization', async function (assert) { + instances.credentialStore.authorized_collection_actions.credentials = []; + await visit(urls.credentials); + + assert.strictEqual( + controller.messageDescription, + intl.t('descriptions.neither-list-nor-create', { + resource: intl.t('resources.credential.title_plural'), + }), + ); + }); }, ); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/credential-stores/credential-store/edit-worker-filter-test.js b/ui/admin/tests/unit/controllers/scopes/scope/credential-stores/credential-store/edit-worker-filter-test.js new file mode 100644 index 0000000000..2a2981eb8c --- /dev/null +++ b/ui/admin/tests/unit/controllers/scopes/scope/credential-stores/credential-store/edit-worker-filter-test.js @@ -0,0 +1,22 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module( + 'Unit | Controller | scopes/scope/credential-stores/credential-store/edit-worker-filter', + function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let controller = this.owner.lookup( + 'controller:scopes/scope/credential-stores/credential-store/edit-worker-filter', + ); + assert.ok(controller); + assert.ok(controller.credentialStores); + }); + }, +); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/credential-stores/credential-store/worker-filter-test.js b/ui/admin/tests/unit/controllers/scopes/scope/credential-stores/credential-store/worker-filter-test.js new file mode 100644 index 0000000000..14ee81ec33 --- /dev/null +++ b/ui/admin/tests/unit/controllers/scopes/scope/credential-stores/credential-store/worker-filter-test.js @@ -0,0 +1,22 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module( + 'Unit | Controller | scopes/scope/credential-stores/credential-store/worker-filter', + function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let controller = this.owner.lookup( + 'controller:scopes/scope/credential-stores/credential-store/worker-filter', + ); + assert.ok(controller); + assert.ok(controller.credentialStores); + }); + }, +); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/credential-stores/index-test.js b/ui/admin/tests/unit/controllers/scopes/scope/credential-stores/index-test.js index 8e89d88f43..1c14e03900 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/credential-stores/index-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/credential-stores/index-test.js @@ -23,6 +23,7 @@ module( setupIndexedDb(hooks); setupIntl(hooks, 'en-us'); + let intl; let controller; let store; let getCredentialStoreCount; @@ -40,8 +41,9 @@ module( credentialStores: null, }; - hooks.beforeEach(function () { - authenticateSession({}); + hooks.beforeEach(async function () { + await authenticateSession({}); + intl = this.owner.lookup('service:intl'); controller = this.owner.lookup( 'controller:scopes/scope/credential-stores/index', ); @@ -138,5 +140,42 @@ module( assert.strictEqual(controller.page, 1); assert.deepEqual(controller.types, selectedItems); }); + + test('messageDescription returns correct translation with list authorization', async function (assert) { + await visit(urls.credentialStores); + + assert.strictEqual( + controller.messageDescription, + intl.t('resources.credential-store.description'), + ); + }); + + test('messageDescription returns correct translation with create authorization', async function (assert) { + instances.scopes.project.authorized_collection_actions[ + 'credential-stores' + ] = ['create']; + await visit(urls.credentialStores); + + assert.strictEqual( + controller.messageDescription, + intl.t('descriptions.create-but-not-list', { + resource: intl.t('resources.credential-store.title_plural'), + }), + ); + }); + + test('messageDescription returns correct translation with no authorization', async function (assert) { + instances.scopes.project.authorized_collection_actions[ + 'credential-stores' + ] = []; + await visit(urls.credentialStores); + + assert.strictEqual( + controller.messageDescription, + intl.t('descriptions.neither-list-nor-create', { + resource: intl.t('resources.credential-store.title_plural'), + }), + ); + }); }, ); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/groups/group/add-members-test.js b/ui/admin/tests/unit/controllers/scopes/scope/groups/group/add-members-test.js index c621b608f9..0784b5ec40 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/groups/group/add-members-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/groups/group/add-members-test.js @@ -32,8 +32,8 @@ module( addMembers: null, }; - hooks.beforeEach(function () { - authenticateSession({}); + hooks.beforeEach(async function () { + await authenticateSession({}); store = this.owner.lookup('service:store'); controller = this.owner.lookup( 'controller:scopes/scope/groups/group/add-members', diff --git a/ui/admin/tests/unit/controllers/scopes/scope/groups/index-test.js b/ui/admin/tests/unit/controllers/scopes/scope/groups/index-test.js index 9b9de64979..4949a96892 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/groups/index-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/groups/index-test.js @@ -17,6 +17,7 @@ module('Unit | Controller | scopes/scope/groups/index', function (hooks) { setupIndexedDb(hooks); setupIntl(hooks, 'en-us'); + let intl; let store; let controller; let getGroupCount; @@ -33,8 +34,9 @@ module('Unit | Controller | scopes/scope/groups/index', function (hooks) { groups: null, }; - hooks.beforeEach(function () { - authenticateSession({}); + hooks.beforeEach(async function () { + await authenticateSession({}); + intl = this.owner.lookup('service:intl'); store = this.owner.lookup('service:store'); controller = this.owner.lookup('controller:scopes/scope/groups/index'); @@ -105,4 +107,37 @@ module('Unit | Controller | scopes/scope/groups/index', function (hooks) { assert.deepEqual(group.member_ids, []); }); + + test('messageDescription returns correct translation with list authorization', async function (assert) { + await visit(urls.groups); + + assert.strictEqual( + controller.messageDescription, + intl.t('resources.group.description'), + ); + }); + + test('messageDescription returns correct translation with create authorization', async function (assert) { + instances.scopes.global.authorized_collection_actions.groups = ['create']; + await visit(urls.groups); + + assert.strictEqual( + controller.messageDescription, + intl.t('descriptions.create-but-not-list', { + resource: intl.t('resources.group.title_plural'), + }), + ); + }); + + test('messageDescription returns correct translation with no authorization', async function (assert) { + instances.scopes.global.authorized_collection_actions.groups = []; + await visit(urls.groups); + + assert.strictEqual( + controller.messageDescription, + intl.t('descriptions.neither-list-nor-create', { + resource: intl.t('resources.group.title_plural'), + }), + ); + }); }); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/host-catalogs/host-catalog/host-sets/host-set/add-hosts-test.js b/ui/admin/tests/unit/controllers/scopes/scope/host-catalogs/host-catalog/host-sets/host-set/add-hosts-test.js index b826eef37f..ad6891833f 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/host-catalogs/host-catalog/host-sets/host-set/add-hosts-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/host-catalogs/host-catalog/host-sets/host-set/add-hosts-test.js @@ -33,8 +33,8 @@ module( addHosts: null, }; - hooks.beforeEach(function () { - authenticateSession({}); + hooks.beforeEach(async function () { + await authenticateSession({}); store = this.owner.lookup('service:store'); controller = this.owner.lookup( 'controller:scopes/scope/host-catalogs/host-catalog/host-sets/host-set/add-hosts', diff --git a/ui/admin/tests/unit/controllers/scopes/scope/host-catalogs/host-catalog/host-sets/host-set/create-and-add-host-test.js b/ui/admin/tests/unit/controllers/scopes/scope/host-catalogs/host-catalog/host-sets/host-set/create-and-add-host-test.js index 46aadf1710..abd36ff9c7 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/host-catalogs/host-catalog/host-sets/host-set/create-and-add-host-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/host-catalogs/host-catalog/host-sets/host-set/create-and-add-host-test.js @@ -33,8 +33,8 @@ module( createAndAddHost: null, }; - hooks.beforeEach(function () { - authenticateSession({}); + hooks.beforeEach(async function () { + await authenticateSession({}); store = this.owner.lookup('service:store'); controller = this.owner.lookup( 'controller:scopes/scope/host-catalogs/host-catalog/host-sets/host-set/create-and-add-host', diff --git a/ui/admin/tests/unit/controllers/scopes/scope/host-catalogs/host-catalog/host-sets/index-test.js b/ui/admin/tests/unit/controllers/scopes/scope/host-catalogs/host-catalog/host-sets/index-test.js index 716afeaf01..aa6c626f52 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/host-catalogs/host-catalog/host-sets/index-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/host-catalogs/host-catalog/host-sets/index-test.js @@ -19,6 +19,7 @@ module( setupIndexedDb(hooks); setupIntl(hooks, 'en-us'); + let intl; let store; let controller; let getHostSetCount; @@ -37,8 +38,9 @@ module( hostSets: null, }; - hooks.beforeEach(function () { - authenticateSession({}); + hooks.beforeEach(async function () { + await authenticateSession({}); + intl = this.owner.lookup('service:intl'); store = this.owner.lookup('service:store'); controller = this.owner.lookup( 'controller:scopes/scope/host-catalogs/host-catalog/host-sets/index', @@ -126,5 +128,40 @@ module( assert.deepEqual(hostSet.host_ids, []); }); + + test('messageDescription returns correct translation with list authorization', async function (assert) { + await visit(urls.hostSets); + + assert.strictEqual( + controller.messageDescription, + intl.t('resources.host-set.description'), + ); + }); + + test('messageDescription returns correct translation with create authorization', async function (assert) { + instances.hostCatalog.authorized_collection_actions['host-sets'] = [ + 'create', + ]; + await visit(urls.hostSets); + + assert.strictEqual( + controller.messageDescription, + intl.t('descriptions.create-but-not-list', { + resource: intl.t('resources.host-set.title_plural'), + }), + ); + }); + + test('messageDescription returns correct translation with no authorization', async function (assert) { + instances.hostCatalog.authorized_collection_actions['host-sets'] = []; + await visit(urls.hostSets); + + assert.strictEqual( + controller.messageDescription, + intl.t('descriptions.neither-list-nor-create', { + resource: intl.t('resources.host-set.title_plural'), + }), + ); + }); }, ); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/host-catalogs/host-catalog/hosts/index-test.js b/ui/admin/tests/unit/controllers/scopes/scope/host-catalogs/host-catalog/hosts/index-test.js index af5bc9db7e..59fdce4d12 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/host-catalogs/host-catalog/hosts/index-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/host-catalogs/host-catalog/hosts/index-test.js @@ -16,6 +16,7 @@ module( setupTest(hooks); setupMirage(hooks); + let intl; let store; let controller; let getHostCount; @@ -34,8 +35,9 @@ module( hosts: null, }; - hooks.beforeEach(function () { - authenticateSession({}); + hooks.beforeEach(async function () { + await authenticateSession({}); + intl = this.owner.lookup('service:intl'); store = this.owner.lookup('service:store'); controller = this.owner.lookup( 'controller:scopes/scope/host-catalogs/host-catalog/hosts/index', @@ -103,5 +105,38 @@ module( assert.strictEqual(getHostCount(), hostCount - 1); }); + + test('messageDescription returns correct translation with list authorization', async function (assert) { + await visit(urls.hosts); + + assert.strictEqual( + controller.messageDescription, + intl.t('resources.host.description'), + ); + }); + + test('messageDescription returns correct translation with create authorization', async function (assert) { + instances.hostCatalog.authorized_collection_actions.hosts = ['create']; + await visit(urls.hosts); + + assert.strictEqual( + controller.messageDescription, + intl.t('descriptions.create-but-not-list', { + resource: intl.t('resources.host.title_plural'), + }), + ); + }); + + test('messageDescription returns correct translation with no authorization', async function (assert) { + instances.hostCatalog.authorized_collection_actions.hosts = []; + await visit(urls.hosts); + + assert.strictEqual( + controller.messageDescription, + intl.t('descriptions.neither-list-nor-create', { + resource: intl.t('resources.host.title_plural'), + }), + ); + }); }, ); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/host-catalogs/index-test.js b/ui/admin/tests/unit/controllers/scopes/scope/host-catalogs/index-test.js index 100549c02f..527f74dd0b 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/host-catalogs/index-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/host-catalogs/index-test.js @@ -19,6 +19,7 @@ module( setupIndexedDb(hooks); setupIntl(hooks, 'en-us'); + let intl; let store; let controller; let getHostCatalogCount; @@ -36,8 +37,9 @@ module( hostCatalogs: null, }; - hooks.beforeEach(function () { - authenticateSession({}); + hooks.beforeEach(async function () { + await authenticateSession({}); + intl = this.owner.lookup('service:intl'); store = this.owner.lookup('service:store'); controller = this.owner.lookup( 'controller:scopes/scope/host-catalogs/index', @@ -114,5 +116,40 @@ module( assert.strictEqual(getHostCatalogCount(), hostCatalogCount - 1); }); + + test('messageDescription returns correct translation with list authorization', async function (assert) { + await visit(urls.hostCatalogs); + + assert.strictEqual( + controller.messageDescription, + intl.t('resources.host-catalog.description'), + ); + }); + + test('messageDescription returns correct translation with create authorization', async function (assert) { + instances.scopes.project.authorized_collection_actions['host-catalogs'] = + ['create']; + await visit(urls.hostCatalogs); + + assert.strictEqual( + controller.messageDescription, + intl.t('descriptions.create-but-not-list', { + resource: intl.t('resources.host-catalog.title_plural'), + }), + ); + }); + + test('messageDescription returns correct translation with no authorization', async function (assert) { + instances.scopes.project.authorized_collection_actions['host-catalogs'] = + []; + await visit(urls.hostCatalogs); + + assert.strictEqual( + controller.messageDescription, + intl.t('descriptions.neither-list-nor-create', { + resource: intl.t('resources.host-catalog.title_plural'), + }), + ); + }); }, ); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/index-test.js b/ui/admin/tests/unit/controllers/scopes/scope/index-test.js index 996e27da95..e4edcd450c 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/index-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/index-test.js @@ -33,8 +33,8 @@ module('Unit | Controller | scopes/scope/index', function (hooks) { globalScope: null, }; - hooks.beforeEach(function () { - authenticateSession({}); + hooks.beforeEach(async function () { + await authenticateSession({}); store = this.owner.lookup('service:store'); controller = this.owner.lookup('controller:scopes/scope/index'); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/policies/index-test.js b/ui/admin/tests/unit/controllers/scopes/scope/policies/index-test.js index fda6a713c8..96d37eb3cd 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/policies/index-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/policies/index-test.js @@ -17,6 +17,7 @@ module('Unit | Controller | scopes/scope/policies/index', function (hooks) { setupIndexedDb(hooks); setupIntl(hooks, 'en-us'); + let intl; let store; let controller; let getPolicyCount; @@ -33,7 +34,8 @@ module('Unit | Controller | scopes/scope/policies/index', function (hooks) { }; hooks.beforeEach(async function () { - authenticateSession({}); + await authenticateSession({}); + intl = this.owner.lookup('service:intl'); store = this.owner.lookup('service:store'); controller = this.owner.lookup('controller:scopes/scope/policies/index'); @@ -84,4 +86,37 @@ module('Unit | Controller | scopes/scope/policies/index', function (hooks) { assert.strictEqual(getPolicyCount(), policyCount - 1); }); + + test('messageDescription returns correct translation with list authorization', async function (assert) { + await visit(urls.policies); + + assert.strictEqual( + controller.messageDescription, + intl.t('resources.policy.messages.none.description'), + ); + }); + + test('messageDescription returns correct translation with create authorization', async function (assert) { + instances.scopes.global.authorized_collection_actions.policies = ['create']; + await visit(urls.policies); + + assert.strictEqual( + controller.messageDescription, + intl.t('descriptions.create-but-not-list', { + resource: intl.t('resources.policy.title_plural'), + }), + ); + }); + + test('messageDescription returns correct translation with no authorization', async function (assert) { + instances.scopes.global.authorized_collection_actions.policies = []; + await visit(urls.policies); + + assert.strictEqual( + controller.messageDescription, + intl.t('descriptions.neither-list-nor-create', { + resource: intl.t('resources.policy.title_plural'), + }), + ); + }); }); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/roles/index-test.js b/ui/admin/tests/unit/controllers/scopes/scope/roles/index-test.js index 3ee4b8beee..cd7779dd66 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/roles/index-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/roles/index-test.js @@ -17,6 +17,7 @@ module('Unit | Controller | scopes/scope/roles/index', function (hooks) { setupIndexedDb(hooks); setupIntl(hooks, 'en-us'); + let intl; let store; let controller; let getRoleCount; @@ -35,7 +36,8 @@ module('Unit | Controller | scopes/scope/roles/index', function (hooks) { }; hooks.beforeEach(async function () { - authenticateSession({}); + await authenticateSession({}); + intl = this.owner.lookup('service:intl'); store = this.owner.lookup('service:store'); controller = this.owner.lookup('controller:scopes/scope/roles/index'); @@ -110,4 +112,37 @@ module('Unit | Controller | scopes/scope/roles/index', function (hooks) { assert.deepEqual(role.principals, []); }); + + test('messageDescription returns correct translation with list authorization', async function (assert) { + await visit(urls.roles); + + assert.strictEqual( + controller.messageDescription, + intl.t('resources.role.description'), + ); + }); + + test('messageDescription returns correct translation with create authorization', async function (assert) { + instances.scopes.global.authorized_collection_actions.roles = ['create']; + await visit(urls.roles); + + assert.strictEqual( + controller.messageDescription, + intl.t('descriptions.create-but-not-list', { + resource: intl.t('resources.role.title_plural'), + }), + ); + }); + + test('messageDescription returns correct translation with no authorization', async function (assert) { + instances.scopes.global.authorized_collection_actions.roles = []; + await visit(urls.roles); + + assert.strictEqual( + controller.messageDescription, + intl.t('descriptions.neither-list-nor-create', { + resource: intl.t('resources.role.title_plural'), + }), + ); + }); }); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/roles/role/add-principals-test.js b/ui/admin/tests/unit/controllers/scopes/scope/roles/role/add-principals-test.js index 8573d9f4df..2e7c236193 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/roles/role/add-principals-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/roles/role/add-principals-test.js @@ -33,7 +33,7 @@ module( }; hooks.beforeEach(async function () { - authenticateSession({}); + await authenticateSession({}); store = this.owner.lookup('service:store'); controller = this.owner.lookup( 'controller:scopes/scope/roles/role/add-principals', diff --git a/ui/admin/tests/unit/controllers/scopes/scope/roles/role/grants-test.js b/ui/admin/tests/unit/controllers/scopes/scope/roles/role/grants-test.js index 808b4b1c0d..b70767d371 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/roles/role/grants-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/roles/role/grants-test.js @@ -28,7 +28,7 @@ module('Unit | Controller | scopes/scope/roles/role/grants', function (hooks) { }; hooks.beforeEach(async function () { - authenticateSession({}); + await authenticateSession({}); store = this.owner.lookup('service:store'); controller = this.owner.lookup('controller:scopes/scope/roles/role/grants'); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/roles/role/manage-scopes/index-test.js b/ui/admin/tests/unit/controllers/scopes/scope/roles/role/manage-scopes/index-test.js index 91b5b55ccf..ad75c104ba 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/roles/role/manage-scopes/index-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/roles/role/manage-scopes/index-test.js @@ -33,7 +33,7 @@ module( }; hooks.beforeEach(async function () { - authenticateSession({}); + await authenticateSession({}); store = this.owner.lookup('service:store'); controller = this.owner.lookup( 'controller:scopes/scope/roles/role/manage-scopes/index', diff --git a/ui/admin/tests/unit/controllers/scopes/scope/roles/role/manage-scopes/manage-custom-scopes-test.js b/ui/admin/tests/unit/controllers/scopes/scope/roles/role/manage-scopes/manage-custom-scopes-test.js index 359c8215d4..5ff98ab61c 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/roles/role/manage-scopes/manage-custom-scopes-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/roles/role/manage-scopes/manage-custom-scopes-test.js @@ -35,7 +35,7 @@ module( }; hooks.beforeEach(async function () { - authenticateSession({}); + await authenticateSession({}); store = this.owner.lookup('service:store'); controller = this.owner.lookup( 'controller:scopes/scope/roles/role/manage-scopes/manage-custom-scopes', diff --git a/ui/admin/tests/unit/controllers/scopes/scope/roles/role/manage-scopes/manage-org-projects-test.js b/ui/admin/tests/unit/controllers/scopes/scope/roles/role/manage-scopes/manage-org-projects-test.js index 5a923458d6..205b85a69f 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/roles/role/manage-scopes/manage-org-projects-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/roles/role/manage-scopes/manage-org-projects-test.js @@ -36,7 +36,7 @@ module( }; hooks.beforeEach(async function () { - authenticateSession({}); + await authenticateSession({}); store = this.owner.lookup('service:store'); controller = this.owner.lookup( 'controller:scopes/scope/roles/role/manage-scopes/manage-org-projects', diff --git a/ui/admin/tests/unit/controllers/scopes/scope/roles/role/scopes-test.js b/ui/admin/tests/unit/controllers/scopes/scope/roles/role/scopes-test.js index 8e3ce4eaf1..4f4c0d590a 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/roles/role/scopes-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/roles/role/scopes-test.js @@ -37,7 +37,7 @@ module('Unit | Controller | scopes/scope/roles/role/scopes', function (hooks) { }; hooks.beforeEach(async function () { - authenticateSession({}); + await authenticateSession({}); controller = this.owner.lookup('controller:scopes/scope/roles/role/scopes'); instances.scopes.global = this.server.create('scope', { id: 'global' }); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/session-recordings/session-recording/channels-by-connection/index-test.js b/ui/admin/tests/unit/controllers/scopes/scope/session-recordings/session-recording/channels-by-connection/index-test.js index 0f9632808b..e8a8909977 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/session-recordings/session-recording/channels-by-connection/index-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/session-recordings/session-recording/channels-by-connection/index-test.js @@ -32,8 +32,8 @@ module( sessionRecording: null, }; - hooks.beforeEach(function () { - authenticateSession({}); + hooks.beforeEach(async function () { + await authenticateSession({}); store = this.owner.lookup('service:store'); controller = this.owner.lookup( 'controller:scopes/scope/session-recordings/session-recording/channels-by-connection/index', diff --git a/ui/admin/tests/unit/controllers/scopes/scope/storage-buckets/index-test.js b/ui/admin/tests/unit/controllers/scopes/scope/storage-buckets/index-test.js index 3772353570..b50fc06c66 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/storage-buckets/index-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/storage-buckets/index-test.js @@ -19,6 +19,8 @@ module( setupIndexedDb(hooks); setupIntl(hooks, 'en-us'); + let intl; + let features; let store; let controller; let getStorageBucketCount; @@ -34,8 +36,10 @@ module( storageBuckets: null, }; - hooks.beforeEach(function () { - authenticateSession({}); + hooks.beforeEach(async function () { + await authenticateSession({}); + intl = this.owner.lookup('service:intl'); + features = this.owner.lookup('service:features'); store = this.owner.lookup('service:store'); controller = this.owner.lookup( 'controller:scopes/scope/storage-buckets/index', @@ -95,5 +99,43 @@ module( assert.strictEqual(getStorageBucketCount(), storageBucketCount - 1); }); + + test('messageDescription returns correct translation with list authorization', async function (assert) { + features.enable('ssh-session-recording'); + await visit(urls.storageBuckets); + + assert.strictEqual( + controller.messageDescription, + intl.t('resources.storage-bucket.messages.none.description'), + ); + }); + + test('messageDescription returns correct translation with create authorization', async function (assert) { + features.enable('ssh-session-recording'); + instances.scopes.global.authorized_collection_actions['storage-buckets'] = + ['create']; + await visit(urls.storageBuckets); + + assert.strictEqual( + controller.messageDescription, + intl.t('descriptions.create-but-not-list', { + resource: intl.t('resources.storage-bucket.title_plural'), + }), + ); + }); + + test('messageDescription returns correct translation with no authorization', async function (assert) { + features.enable('ssh-session-recording'); + instances.scopes.global.authorized_collection_actions['storage-buckets'] = + []; + await visit(urls.storageBuckets); + + assert.strictEqual( + controller.messageDescription, + intl.t('descriptions.neither-list-nor-create', { + resource: intl.t('resources.storage-bucket.title_plural'), + }), + ); + }); }, ); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/targets/index-test.js b/ui/admin/tests/unit/controllers/scopes/scope/targets/index-test.js index bafba80cfd..2a8100d39d 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/targets/index-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/targets/index-test.js @@ -18,6 +18,7 @@ module('Unit | Controller | scopes/scope/targets/index', function (hooks) { setupIndexedDb(hooks); setupIntl(hooks, 'en-us'); + let intl; let store; let controller; let getTargetCount; @@ -42,8 +43,9 @@ module('Unit | Controller | scopes/scope/targets/index', function (hooks) { alias: null, }; - hooks.beforeEach(function () { - authenticateSession({}); + hooks.beforeEach(async function () { + await authenticateSession({}); + intl = this.owner.lookup('service:intl'); store = this.owner.lookup('service:store'); controller = this.owner.lookup('controller:scopes/scope/targets/index'); @@ -286,4 +288,37 @@ module('Unit | Controller | scopes/scope/targets/index', function (hooks) { assert.strictEqual(target.egress_worker_filter, 'test'); }); + + test('messageDescription returns correct translation with list authorization', async function (assert) { + await visit(urls.targets); + + assert.strictEqual( + controller.messageDescription, + intl.t('resources.target.description'), + ); + }); + + test('messageDescription returns correct translation with create authorization', async function (assert) { + instances.scopes.project.authorized_collection_actions.targets = ['create']; + await visit(urls.targets); + + assert.strictEqual( + controller.messageDescription, + intl.t('descriptions.create-but-not-list', { + resource: intl.t('resources.target.title_plural'), + }), + ); + }); + + test('messageDescription returns correct translation with no authorization', async function (assert) { + instances.scopes.project.authorized_collection_actions.targets = []; + await visit(urls.targets); + + assert.strictEqual( + controller.messageDescription, + intl.t('descriptions.neither-list-nor-create', { + resource: intl.t('resources.target.title_plural'), + }), + ); + }); }); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/targets/target/add-brokered-credential-sources-test.js b/ui/admin/tests/unit/controllers/scopes/scope/targets/target/add-brokered-credential-sources-test.js index ef1c0e57e9..2f2ef602b1 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/targets/target/add-brokered-credential-sources-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/targets/target/add-brokered-credential-sources-test.js @@ -34,8 +34,8 @@ module( credentialSources: null, }; - hooks.beforeEach(function () { - authenticateSession({}); + hooks.beforeEach(async function () { + await authenticateSession({}); store = this.owner.lookup('service:store'); controller = this.owner.lookup( 'controller:scopes/scope/targets/target/add-brokered-credential-sources', diff --git a/ui/admin/tests/unit/controllers/scopes/scope/targets/target/add-host-sources-test.js b/ui/admin/tests/unit/controllers/scopes/scope/targets/target/add-host-sources-test.js index ee166228c9..4c43549c6a 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/targets/target/add-host-sources-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/targets/target/add-host-sources-test.js @@ -35,8 +35,8 @@ module( hostSources: null, }; - hooks.beforeEach(function () { - authenticateSession({}); + hooks.beforeEach(async function () { + await authenticateSession({}); store = this.owner.lookup('service:store'); controller = this.owner.lookup( 'controller:scopes/scope/targets/target/add-host-sources', diff --git a/ui/admin/tests/unit/controllers/scopes/scope/targets/target/add-injected-application-credential-sources-test.js b/ui/admin/tests/unit/controllers/scopes/scope/targets/target/add-injected-application-credential-sources-test.js index 623f27c11a..efda0ae043 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/targets/target/add-injected-application-credential-sources-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/targets/target/add-injected-application-credential-sources-test.js @@ -34,8 +34,8 @@ module( credentialSources: null, }; - hooks.beforeEach(function () { - authenticateSession({}); + hooks.beforeEach(async function () { + await authenticateSession({}); store = this.owner.lookup('service:store'); controller = this.owner.lookup( 'controller:scopes/scope/targets/target/add-injected-application-credential-sources', diff --git a/ui/admin/tests/unit/controllers/scopes/scope/targets/target/enable-session-recording/create-storage-bucket-test.js b/ui/admin/tests/unit/controllers/scopes/scope/targets/target/enable-session-recording/create-storage-bucket-test.js index 5786fcba3e..609e6b7665 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/targets/target/enable-session-recording/create-storage-bucket-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/targets/target/enable-session-recording/create-storage-bucket-test.js @@ -33,8 +33,8 @@ module( createStorageBucket: null, }; - hooks.beforeEach(function () { - authenticateSession({}); + hooks.beforeEach(async function () { + await authenticateSession({}); store = this.owner.lookup('service:store'); controller = this.owner.lookup( 'controller:scopes/scope/targets/target/enable-session-recording/create-storage-bucket', diff --git a/ui/admin/tests/unit/controllers/scopes/scope/targets/target/enable-session-recording/index-test.js b/ui/admin/tests/unit/controllers/scopes/scope/targets/target/enable-session-recording/index-test.js index 4f34bc50fb..59b8e9f86c 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/targets/target/enable-session-recording/index-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/targets/target/enable-session-recording/index-test.js @@ -32,8 +32,8 @@ module( enableSessionRecording: null, }; - hooks.beforeEach(function () { - authenticateSession({}); + hooks.beforeEach(async function () { + await authenticateSession({}); store = this.owner.lookup('service:store'); controller = this.owner.lookup( 'controller:scopes/scope/targets/target/enable-session-recording/index', diff --git a/ui/admin/tests/unit/controllers/scopes/scope/users/index-test.js b/ui/admin/tests/unit/controllers/scopes/scope/users/index-test.js index d4228b47fb..5e47440daa 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/users/index-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/users/index-test.js @@ -17,6 +17,7 @@ module('Unit | Controller | scopes/scope/users/index', function (hooks) { setupIndexedDb(hooks); setupIntl(hooks, 'en-us'); + let intl; let store; let controller; let getUserCount; @@ -33,8 +34,9 @@ module('Unit | Controller | scopes/scope/users/index', function (hooks) { users: null, }; - hooks.beforeEach(function () { - authenticateSession({}); + hooks.beforeEach(async function () { + await authenticateSession({}); + intl = this.owner.lookup('service:intl'); store = this.owner.lookup('service:store'); controller = this.owner.lookup('controller:scopes/scope/users/index'); @@ -105,4 +107,37 @@ module('Unit | Controller | scopes/scope/users/index', function (hooks) { assert.deepEqual(user.account_ids, []); }); + + test('messageDescription returns correct translation with list authorization', async function (assert) { + await visit(urls.users); + + assert.strictEqual( + controller.messageDescription, + intl.t('resources.user.description'), + ); + }); + + test('messageDescription returns correct translation with create authorization', async function (assert) { + instances.scopes.global.authorized_collection_actions.users = ['create']; + await visit(urls.users); + + assert.strictEqual( + controller.messageDescription, + intl.t('descriptions.create-but-not-list', { + resource: intl.t('resources.user.title_plural'), + }), + ); + }); + + test('messageDescription returns correct translation with no authorization', async function (assert) { + instances.scopes.global.authorized_collection_actions.users = []; + await visit(urls.users); + + assert.strictEqual( + controller.messageDescription, + intl.t('descriptions.neither-list-nor-create', { + resource: intl.t('resources.user.title_plural'), + }), + ); + }); }); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/users/user/add-accounts-test.js b/ui/admin/tests/unit/controllers/scopes/scope/users/user/add-accounts-test.js index 17d917f5ba..f4b6a05acb 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/users/user/add-accounts-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/users/user/add-accounts-test.js @@ -31,8 +31,8 @@ module( addAccounts: null, }; - hooks.beforeEach(function () { - authenticateSession({}); + hooks.beforeEach(async function () { + await authenticateSession({}); store = this.owner.lookup('service:store'); controller = this.owner.lookup( 'controller:scopes/scope/users/user/add-accounts', diff --git a/ui/admin/tests/unit/controllers/scopes/scope/workers/index-test.js b/ui/admin/tests/unit/controllers/scopes/scope/workers/index-test.js index c80f0a8e4c..8c445a1600 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/workers/index-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/workers/index-test.js @@ -15,6 +15,7 @@ module('Unit | Controller | scopes/scope/workers/index', function (hooks) { setupMirage(hooks); setupIntl(hooks, 'en-us'); + let intl; let store; let controller; let getWorkerCount; @@ -31,8 +32,9 @@ module('Unit | Controller | scopes/scope/workers/index', function (hooks) { workers: null, }; - hooks.beforeEach(function () { - authenticateSession({}); + hooks.beforeEach(async function () { + await authenticateSession({}); + intl = this.owner.lookup('service:intl'); store = this.owner.lookup('service:store'); controller = this.owner.lookup('controller:scopes/scope/workers/index'); @@ -109,4 +111,39 @@ module('Unit | Controller | scopes/scope/workers/index', function (hooks) { assert.deepEqual(route.tags, []); }); + + test('messageDescription returns correct translation with list authorization', async function (assert) { + await visit(urls.workers); + + assert.strictEqual( + controller.messageDescription, + intl.t('resources.worker.description'), + ); + }); + + test('messageDescription returns correct translation with create authorization', async function (assert) { + instances.scopes.global.authorized_collection_actions.workers = [ + 'create:worker-led', + ]; + await visit(urls.workers); + + assert.strictEqual( + controller.messageDescription, + intl.t('descriptions.create-but-not-list', { + resource: intl.t('titles.workers'), + }), + ); + }); + + test('messageDescription returns correct translation with no authorization', async function (assert) { + instances.scopes.global.authorized_collection_actions.workers = []; + await visit(urls.workers); + + assert.strictEqual( + controller.messageDescription, + intl.t('descriptions.neither-list-nor-create', { + resource: intl.t('titles.workers'), + }), + ); + }); }); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/workers/new-test.js b/ui/admin/tests/unit/controllers/scopes/scope/workers/new-test.js index a033e6940b..3896a97fd7 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/workers/new-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/workers/new-test.js @@ -27,8 +27,8 @@ module('Unit | Controller | scopes/scope/workers/new', function (hooks) { workers: null, }; - hooks.beforeEach(function () { - authenticateSession({}); + hooks.beforeEach(async function () { + await authenticateSession({}); store = this.owner.lookup('service:store'); controller = this.owner.lookup('controller:scopes/scope/workers/new'); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/workers/worker/create-tags-test.js b/ui/admin/tests/unit/controllers/scopes/scope/workers/worker/create-tags-test.js index e65a32dae8..0e8cf7a898 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/workers/worker/create-tags-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/workers/worker/create-tags-test.js @@ -31,8 +31,8 @@ module( createTags: null, }; - hooks.beforeEach(function () { - authenticateSession({}); + hooks.beforeEach(async function () { + await authenticateSession({}); store = this.owner.lookup('service:store'); controller = this.owner.lookup( 'controller:scopes/scope/workers/worker/create-tags', diff --git a/ui/admin/tests/unit/controllers/scopes/scope/workers/worker/tags-test.js b/ui/admin/tests/unit/controllers/scopes/scope/workers/worker/tags-test.js index f75e28a438..1824c353a1 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/workers/worker/tags-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/workers/worker/tags-test.js @@ -34,8 +34,8 @@ module( tags: null, }; - hooks.beforeEach(function () { - authenticateSession({}); + hooks.beforeEach(async function () { + await authenticateSession({}); store = this.owner.lookup('service:store'); controller = this.owner.lookup( 'controller:scopes/scope/workers/worker/tags', diff --git a/ui/admin/tests/unit/instance-initializers/feature-edition-test.js b/ui/admin/tests/unit/instance-initializers/feature-edition-test.js index b4da0e0649..e00c8ba023 100644 --- a/ui/admin/tests/unit/instance-initializers/feature-edition-test.js +++ b/ui/admin/tests/unit/instance-initializers/feature-edition-test.js @@ -30,12 +30,12 @@ module('Unit | Instance Initializer | feature-edition', function (hooks) { this.instance = this.application.buildInstance(); }); + hooks.afterEach(function () { run(this.instance, 'destroy'); run(this.application, 'destroy'); }); - // TODO: Replace this with your real tests. test('it works', async function (assert) { await this.instance.boot(); diff --git a/ui/admin/tests/unit/routes/scopes/scope/credential-stores/credential-store/edit-worker-filter-test.js b/ui/admin/tests/unit/routes/scopes/scope/credential-stores/credential-store/edit-worker-filter-test.js new file mode 100644 index 0000000000..8de8cb806b --- /dev/null +++ b/ui/admin/tests/unit/routes/scopes/scope/credential-stores/credential-store/edit-worker-filter-test.js @@ -0,0 +1,21 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { module, test } from 'qunit'; +import { setupTest } from 'admin/tests/helpers'; + +module( + 'Unit | Route | scopes/scope/credential-stores/credential-store/edit-worker-filter', + function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let route = this.owner.lookup( + 'route:scopes/scope/credential-stores/credential-store/edit-worker-filter', + ); + assert.ok(route); + }); + }, +); diff --git a/ui/admin/tests/unit/routes/scopes/scope/credential-stores/credential-store/worker-filter-test.js b/ui/admin/tests/unit/routes/scopes/scope/credential-stores/credential-store/worker-filter-test.js new file mode 100644 index 0000000000..23f4e1eb3e --- /dev/null +++ b/ui/admin/tests/unit/routes/scopes/scope/credential-stores/credential-store/worker-filter-test.js @@ -0,0 +1,21 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { module, test } from 'qunit'; +import { setupTest } from 'admin/tests/helpers'; + +module( + 'Unit | Route | scopes/scope/credential-stores/credential-store/worker-filter', + function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let route = this.owner.lookup( + 'route:scopes/scope/credential-stores/credential-store/worker-filter', + ); + assert.ok(route); + }); + }, +); diff --git a/ui/admin/tests/unit/services/feature-edition-test.js b/ui/admin/tests/unit/services/feature-edition-test.js index 9901dc9d33..913b958556 100644 --- a/ui/admin/tests/unit/services/feature-edition-test.js +++ b/ui/admin/tests/unit/services/feature-edition-test.js @@ -9,7 +9,6 @@ import { setupTest } from 'admin/tests/helpers'; module('Unit | Service | features', function (hooks) { setupTest(hooks); - // TODO: Replace this with your real tests. test('it exists', function (assert) { let service = this.owner.lookup('service:features'); assert.ok(service); diff --git a/ui/admin/tests/unit/services/session-test.js b/ui/admin/tests/unit/services/session-test.js index 9b53ab8f40..667431e4e0 100644 --- a/ui/admin/tests/unit/services/session-test.js +++ b/ui/admin/tests/unit/services/session-test.js @@ -9,7 +9,6 @@ import { setupTest } from 'admin/tests/helpers'; module('Unit | Service | session', function (hooks) { setupTest(hooks); - // TODO: Replace this with your real tests. test('it exists', function (assert) { let service = this.owner.lookup('service:session'); assert.ok(service); diff --git a/ui/desktop/app/controllers/scopes/scope/authenticate/method/oidc.js b/ui/desktop/app/controllers/scopes/scope/authenticate/method/oidc.js index c64b230c31..31bbec8799 100644 --- a/ui/desktop/app/controllers/scopes/scope/authenticate/method/oidc.js +++ b/ui/desktop/app/controllers/scopes/scope/authenticate/method/oidc.js @@ -3,21 +3,34 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import Controller from '@ember/controller'; +import Controller, { inject as controller } from '@ember/controller'; +import { action } from '@ember/object'; import { inject as service } from '@ember/service'; export default class ScopesScopeAuthenticateMethodOidcController extends Controller { + @controller('scopes/scope/authenticate/method/index') authenticateMethod; + // =services @service session; - // =attributes + // =actions /** - * Authentication URL for the pending OIDC flow, if any. - * @type {?string} + * Retry by starting a new OIDC authentication flow. + * @param {object} e */ - get authURL() { - return this.session.data.pending.oidc.attributes.auth_url; + @action + async retryAuthentication(e) { + // TODO: This event handler can be removed when rose dialog gets + // refactored to use hds. + e.preventDefault(); + + const scope = this.authMethod.scope; + const authenticatorName = `authenticator:${this.authMethod.type}`; + await this.authenticateMethod.startOIDCAuthentication(authenticatorName, { + scope, + authMethod: this.authMethod, + }); } } diff --git a/ui/desktop/app/routes/scopes/scope/authenticate/method/oidc.js b/ui/desktop/app/routes/scopes/scope/authenticate/method/oidc.js index 0c50090f5d..1ca7eccc36 100644 --- a/ui/desktop/app/routes/scopes/scope/authenticate/method/oidc.js +++ b/ui/desktop/app/routes/scopes/scope/authenticate/method/oidc.js @@ -31,6 +31,16 @@ export default class ScopesScopeAuthenticateMethodOidcRoute extends Route { return oidc.attemptFetchToken({ scope, authMethod }); } + /** + * Adds the auth-method to the controller context. + * @param {Controller} controller + */ + setupController(controller) { + super.setupController(...arguments); + const authMethod = this.modelFor('scopes.scope.authenticate.method'); + controller.authMethod = authMethod; + } + @runEvery(POLL_TIMEOUT_SECONDS * 1000) poller() { this.refresh(); diff --git a/ui/desktop/app/styles/app.scss b/ui/desktop/app/styles/app.scss index dadfe07e62..8ae7b56c00 100644 --- a/ui/desktop/app/styles/app.scss +++ b/ui/desktop/app/styles/app.scss @@ -124,21 +124,6 @@ } } -.rose-table { - p { - margin-bottom: 0; - } - - .rose-form-checkbox { - margin-bottom: 0; - - .rose-form-checkbox-label-text { - color: var(--stark); - text-decoration: underline; - } - } -} - .rose-header { -webkit-app-region: drag; min-height: sizing.rems(l) * 2; diff --git a/ui/desktop/app/templates/cluster-url.hbs b/ui/desktop/app/templates/cluster-url.hbs index 91f9975e0e..2dc38ba70c 100644 --- a/ui/desktop/app/templates/cluster-url.hbs +++ b/ui/desktop/app/templates/cluster-url.hbs @@ -15,10 +15,12 @@ @disabled={{(is-loading)}} as |form| > + + {{! template-lint-disable no-autofocus-attribute }} diff --git a/ui/desktop/app/templates/error.hbs b/ui/desktop/app/templates/error.hbs index 465e90b0aa..d3bbc7c8d5 100644 --- a/ui/desktop/app/templates/error.hbs +++ b/ui/desktop/app/templates/error.hbs @@ -8,7 +8,7 @@
- + diff --git a/ui/desktop/app/templates/scopes/scope/authenticate.hbs b/ui/desktop/app/templates/scopes/scope/authenticate.hbs index e9d6953e7f..dea545b3fd 100644 --- a/ui/desktop/app/templates/scopes/scope/authenticate.hbs +++ b/ui/desktop/app/templates/scopes/scope/authenticate.hbs @@ -49,16 +49,15 @@ {{/if}} {{#unless @model.authMethods}} - - - {{t 'resources.auth-method.messages.none.description'}} - - + + + + {{/unless}}

diff --git a/ui/desktop/app/templates/scopes/scope/authenticate/index.hbs b/ui/desktop/app/templates/scopes/scope/authenticate/index.hbs index e7be45d82c..4e61d82f50 100644 --- a/ui/desktop/app/templates/scopes/scope/authenticate/index.hbs +++ b/ui/desktop/app/templates/scopes/scope/authenticate/index.hbs @@ -5,14 +5,12 @@ {{#unless @model}} - - - {{t 'resources.auth-method.messages.none.description'}} - - + + + + {{/unless}} \ No newline at end of file diff --git a/ui/desktop/app/templates/scopes/scope/authenticate/method/oidc.hbs b/ui/desktop/app/templates/scopes/scope/authenticate/method/oidc.hbs index 00f529749b..04724a8503 100644 --- a/ui/desktop/app/templates/scopes/scope/authenticate/method/oidc.hbs +++ b/ui/desktop/app/templates/scopes/scope/authenticate/method/oidc.hbs @@ -11,7 +11,7 @@

{{t 'resources.auth-method.messages.pending.description'}}

{{t 'resources.auth-method.questions.no-see-window'}} - + {{t 'actions.retry'}}

diff --git a/ui/desktop/app/templates/scopes/scope/projects/error.hbs b/ui/desktop/app/templates/scopes/scope/projects/error.hbs index 1699834955..623b4f2018 100644 --- a/ui/desktop/app/templates/scopes/scope/projects/error.hbs +++ b/ui/desktop/app/templates/scopes/scope/projects/error.hbs @@ -9,7 +9,10 @@ {{#if @model.errors}} {{#each @model.errors as |error|}} - + {{/each}} {{/if}} @@ -17,7 +20,7 @@ {{! But a disconnected state doesn't get normalized errors }} {{#unless @model.errors}} - + diff --git a/ui/desktop/config/environment.js b/ui/desktop/config/environment.js index 801c944cfd..7a46410790 100644 --- a/ui/desktop/config/environment.js +++ b/ui/desktop/config/environment.js @@ -53,6 +53,7 @@ module.exports = function (environment) { '/tutorials/oss-getting-started/oss-getting-started-desktop-app#connect', sessions: '/tutorials/oss-getting-started/oss-getting-started-desktop-app#connect', + 'support-page': 'https://support.hashicorp.com/hc/en-us', }, }, diff --git a/ui/desktop/electron-app/src/index.js b/ui/desktop/electron-app/src/index.js index 131c672f5e..e5118aa7a7 100644 --- a/ui/desktop/electron-app/src/index.js +++ b/ui/desktop/electron-app/src/index.js @@ -163,7 +163,8 @@ const createWindow = (partition, closeWindowCB) => { if ( isLocalhost(url) || url.startsWith('https://developer.hashicorp.com/') || - url.startsWith('https://releases.hashicorp.com/boundary-desktop/') + url.startsWith('https://releases.hashicorp.com/boundary-desktop/') || + url.startsWith('https://support.hashicorp.com/hc/en-us') ) { shell.openExternal(url); } diff --git a/ui/desktop/package.json b/ui/desktop/package.json index a151e3171e..b7fcb432e4 100644 --- a/ui/desktop/package.json +++ b/ui/desktop/package.json @@ -43,7 +43,7 @@ "dependencies": { "ember-named-blocks-polyfill": "^0.2.5", "lodash": "^4.17.21", - "uuid": "^9.0.0", + "uuid": "^11.0.3", "@xterm/xterm": "^5.5.0", "@xterm/addon-canvas": "^0.7.0", "@xterm/addon-fit": "^0.10.0" @@ -60,7 +60,6 @@ "@faker-js/faker": "^8.0.2", "@glimmer/component": "^1.1.2", "@glimmer/tracking": "^1.1.2", - "@playwright/test": "^1.43.0", "babel-loader": "^9.2.1", "broccoli-asset-rev": "^3.0.0", "concurrently": "^9.1.0", @@ -69,9 +68,7 @@ "doctoc": "^2.2.0", "ember-a11y-testing": "^7.0.0", "ember-auto-import": "^2.8.1", - "ember-autofocus-modifier": "^4.0.1", "ember-cli": "^5.12.0", - "ember-cli-app-version": "^6.0.0", "ember-cli-babel": "^8.2.0", "ember-cli-code-coverage": "^1.0.3", "ember-cli-dependency-checker": "^3.3.2", @@ -101,14 +98,13 @@ "eslint-plugin-qunit": "^8.0.1", "js-bexpr": "hashicorp/js-bexpr#9b4a4b54d85eba67fdfc0990133d1518d890b1e1", "loader.js": "^4.7.0", - "playwright": "^1.43.0", "prettier": "^3.0.0", "qunit": "^2.22.0", "qunit-dom": "^3.2.1", "rose": "*", "sass": "^1.69.5", "shx": "^0.3.4", - "sinon": "^18.0.0", + "sinon": "^19.0.2", "stylelint": "^15.10.1", "stylelint-config-prettier-scss": "^0.0.1", "stylelint-config-standard": "^32.0.0", diff --git a/ui/desktop/tests/acceptance/application-test.js b/ui/desktop/tests/acceptance/application-test.js index 1bc56832dd..f841c4c34a 100644 --- a/ui/desktop/tests/acceptance/application-test.js +++ b/ui/desktop/tests/acceptance/application-test.js @@ -24,8 +24,8 @@ module('Acceptance | index', function (hooks) { clusterUrl: null, }; - hooks.beforeEach(function () { - invalidateSession(); + hooks.beforeEach(async function () { + await invalidateSession(); urls.clusterUrl = '/cluster-url'; diff --git a/ui/desktop/tests/acceptance/authentication-test.js b/ui/desktop/tests/acceptance/authentication-test.js index e84dc69ca8..3f175756a5 100644 --- a/ui/desktop/tests/acceptance/authentication-test.js +++ b/ui/desktop/tests/acceptance/authentication-test.js @@ -64,12 +64,12 @@ module('Acceptance | authentication', function (hooks) { clusterUrl.rendererClusterUrl = windowOrigin; }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { instances.user = this.server.create('user', { scope: instances.scopes.global, }); - invalidateSession(); + await invalidateSession(); // create scopes instances.scopes.global = this.server.create('scope', { id: 'global' }); @@ -193,7 +193,7 @@ module('Acceptance | authentication', function (hooks) { test('401 responses result in deauthentication', async function (assert) { assert.expect(3); - authenticateSession({ + await authenticateSession({ scope: { id: instances.scopes.global.id, type: instances.scopes.global.type, diff --git a/ui/desktop/tests/acceptance/cluster-url-test.js b/ui/desktop/tests/acceptance/cluster-url-test.js index b5d6be6015..14d7db3801 100644 --- a/ui/desktop/tests/acceptance/cluster-url-test.js +++ b/ui/desktop/tests/acceptance/cluster-url-test.js @@ -63,8 +63,8 @@ module('Acceptance | clusterUrl', function (hooks) { mockIPC = test.owner.lookup('service:browser/window').mockIPC; }; - hooks.beforeEach(function () { - invalidateSession(); + hooks.beforeEach(async function () { + await invalidateSession(); // create scopes instances.scopes.global = this.server.create('scope', { id: 'global' }); diff --git a/ui/desktop/tests/acceptance/projects-test.js b/ui/desktop/tests/acceptance/projects-test.js index 65d8515f1c..3574276c50 100644 --- a/ui/desktop/tests/acceptance/projects-test.js +++ b/ui/desktop/tests/acceptance/projects-test.js @@ -58,8 +58,8 @@ module('Acceptance | projects', function (hooks) { clusterUrl.rendererClusterUrl = windowOrigin; }; - hooks.beforeEach(function () { - authenticateSession({ username: 'admin' }); + hooks.beforeEach(async function () { + await authenticateSession({ username: 'admin' }); // create scopes instances.scopes.global = this.server.create('scope', { id: 'global' }); @@ -102,7 +102,7 @@ module('Acceptance | projects', function (hooks) { }); test('visiting index while unauthenticated redirects to global authenticate method', async function (assert) { - invalidateSession(); + await invalidateSession(); assert.expect(2); await visit(urls.projects); await a11yAudit(); diff --git a/ui/desktop/tests/acceptance/projects/sessions/index-test.js b/ui/desktop/tests/acceptance/projects/sessions/index-test.js index 719a31f1da..91bc008d61 100644 --- a/ui/desktop/tests/acceptance/projects/sessions/index-test.js +++ b/ui/desktop/tests/acceptance/projects/sessions/index-test.js @@ -74,12 +74,15 @@ module('Acceptance | projects | sessions | index', function (hooks) { clusterUrl.rendererClusterUrl = windowOrigin; }; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { instances.user = this.server.create('user', { scope: instances.scopes.global, }); - authenticateSession({ user_id: instances.user.id, username: 'admin' }); + await authenticateSession({ + user_id: instances.user.id, + username: 'admin', + }); // create scopes instances.scopes.global = this.server.create('scope', { @@ -163,7 +166,7 @@ module('Acceptance | projects | sessions | index', function (hooks) { }); test('visiting index while unauthenticated redirects to global authenticate method', async function (assert) { - invalidateSession(); + await invalidateSession(); this.stubCacheDaemonSearch(); await visit(urls.sessions); diff --git a/ui/desktop/tests/acceptance/projects/sessions/session-test.js b/ui/desktop/tests/acceptance/projects/sessions/session-test.js index c2d48c5f85..0325fecfd5 100644 --- a/ui/desktop/tests/acceptance/projects/sessions/session-test.js +++ b/ui/desktop/tests/acceptance/projects/sessions/session-test.js @@ -56,12 +56,15 @@ module('Acceptance | projects | sessions | session', function (hooks) { let originalUncaughtException = QUnit.onUncaughtException; - hooks.beforeEach(function () { + hooks.beforeEach(async function () { instances.user = this.server.create('user', { scope: instances.scopes.global, }); - authenticateSession({ user_id: instances.user.id, username: 'admin' }); + await authenticateSession({ + user_id: instances.user.id, + username: 'admin', + }); // create scopes instances.scopes.global = this.server.create('scope', { id: 'global' }); @@ -145,13 +148,125 @@ module('Acceptance | projects | sessions | session', function (hooks) { await click(TARGET_CONNECT_BUTTON); assert.strictEqual(currentURL(), urls.session); - assert.dom('credential-panel-header').doesNotExist(); - assert.dom('credential-panel-body').doesNotExist(); + assert.dom('.credential-panel-header').doesNotExist(); + assert.dom('.credential-panel-body').doesNotExist(); assert .dom('[data-test-no-credentials]') .hasText(`Connected You can now access ${instances.target.name}`); }); + test('visiting session with vault type credentials should display nested data in a key/value format without escape characters', async function (assert) { + this.ipcStub.withArgs('cliExists').returns(true); + this.ipcStub.withArgs('connect').returns({ + session_id: instances.session.id, + address: 'a_123', + port: 'p_123', + protocol: 'tcp', + credentials: [ + { + credential_source: { + id: 'clvlt_4cvscMTl0N', + name: 'Credential Library 0', + description: 'Source Description', + credential_store_id: 'csvlt_Q1HFGt7Jpm', + type: 'vault-generic', + }, + secret: { + raw: 'eyJhcnJheSI6WyJvbmUiLCJ0d28iLCJ0aHJlZSIsIm9uZSIsInR3byIsInRocmVlIiwib25lIiwidHdvIiwidGhyZWUiLCJvbmUiLCJ0d28iLCJ0aHJlZSJdLCJuZXN0ZWQiOnsiYm9vbCI6dHJ1ZSwibG9uZyI6IjEyMjM1MzQ1NmFzZWRmYTQzd3J0ZjIzNGYyM2FzZGdmYXNkZnJnYXdzZWZhd3NlZnNkZjQiLCJzZWNlcmV0Ijoic28gbmVzdGVkIn0sInRlc3QiOiJwaHJhc2UifQ', + decoded: { + data: { + backslash: 'password\\with\\tslash', + email: { + address: 'test.com', + }, + }, + metadata: { + created_time: '2024-04-12T18:38:36.226715555Z', + custom_metadata: null, + deletion_time: '', + destroyed: false, + version: 8, + }, + }, + }, + }, + ], + }); + + await visit(urls.target); + await click(TARGET_CONNECT_BUTTON); + assert.strictEqual(currentURL(), urls.session); + + assert.dom('.credential-panel-header').exists(); + assert.dom('.credential-panel-body').exists(); + assert.dom('.credential-secret').exists(); + + // Check if nested data is displayed in a key/value format without escape characters + assert.dom('.secret-container:nth-of-type(1)').includesText('backslash'); + await click('.secret-container:nth-of-type(1) .hds-icon'); + const expectedOutput = String.raw`password\with\tslash`; + assert + .dom('.secret-container:nth-of-type(1) .secret-content') + .hasText(expectedOutput); + + assert + .dom('.secret-container:nth-of-type(2)') + .includesText('email.address'); + await click('.secret-container:nth-of-type(2) .hds-icon'); + assert + .dom('.secret-container:nth-of-type(2) .secret-content') + .hasText('test.com'); + }); + + test('visiting session with static type credentials should display nested data in a key/value format without escape characters', async function (assert) { + this.ipcStub.withArgs('cliExists').returns(true); + this.ipcStub.withArgs('connect').returns({ + session_id: instances.session.id, + address: 'a_123', + port: 'p_123', + protocol: 'tcp', + credentials: [ + { + credential_source: { + id: 'credjson_7cKBbBEkC3', + credential_store_id: 'csst_yzlsot2pum', + type: 'static', + credential_type: 'json', + }, + secret: { + raw: 'eyJhcnJheSI6WyJvbmUiLCJ0d28iLCJ0aHJlZSIsIm9uZSIsInR3byIsInRocmVlIiwib25lIiwidHdvIiwidGhyZWUiLCJvbmUiLCJ0d28iLCJ0aHJlZSJdLCJuZXN0ZWQiOnsiYm9vbCI6dHJ1ZSwibG9uZyI6IjEyMjM1MzQ1NmFzZWRmYTQzd3J0ZjIzNGYyM2FzZGdmYXNkZnJnYXdzZWZhd3NlZnNkZjQiLCJzZWNlcmV0Ijoic28gbmVzdGVkIn0sInRlc3QiOiJwaHJhc2UifQ==', + decoded: { + nested_secret: { + complex_nest: { + blackslash: 'password\\with\\tslash', + }, + }, + }, + }, + }, + ], + }); + + await visit(urls.target); + await click(TARGET_CONNECT_BUTTON); + assert.strictEqual(currentURL(), urls.session); + + assert.dom('.credential-panel-header').exists(); + assert.dom('.credential-panel-body').exists(); + assert.dom('.credential-secret').exists(); + + // Check if nested data is displayed in a key/value format without escape characters + const expectedOutput = String.raw`password\with\tslash`; + assert + .dom('.secret-container:nth-of-type(1)') + .includesText('nested_secret.complex_nest.blackslash'); + await click('.secret-container:nth-of-type(1) .hds-icon'); + + assert + .dom('.secret-container:nth-of-type(1) .secret-content') + .hasText(expectedOutput); + }); + test('visiting a session that does not have permissions to read a host', async function (assert) { assert.expect(1); this.server.get('/hosts/:id', () => new Response(403)); diff --git a/ui/desktop/tests/acceptance/projects/settings/index-test.js b/ui/desktop/tests/acceptance/projects/settings/index-test.js index 07eb5ca35f..f99257e2e2 100644 --- a/ui/desktop/tests/acceptance/projects/settings/index-test.js +++ b/ui/desktop/tests/acceptance/projects/settings/index-test.js @@ -63,8 +63,8 @@ module('Acceptance | projects | settings | index', function (hooks) { clusterUrl.rendererClusterUrl = windowOrigin; }; - hooks.beforeEach(function () { - authenticateSession(); + hooks.beforeEach(async function () { + await authenticateSession(); // Generate scopes instances.scopes.global = this.server.create('scope', { id: 'global', @@ -134,7 +134,7 @@ module('Acceptance | projects | settings | index', function (hooks) { }); test('clicking sign-out button logs out the user', async function (assert) { - authenticateSession({ username: 'testuser' }); + await authenticateSession({ username: 'testuser' }); assert.expect(2); await visit(urls.settings); assert.ok(currentSession().isAuthenticated); diff --git a/ui/desktop/tests/acceptance/projects/targets/index-test.js b/ui/desktop/tests/acceptance/projects/targets/index-test.js index 5ba046f5e5..83cde86623 100644 --- a/ui/desktop/tests/acceptance/projects/targets/index-test.js +++ b/ui/desktop/tests/acceptance/projects/targets/index-test.js @@ -82,8 +82,8 @@ module('Acceptance | projects | targets | index', function (hooks) { clusterUrl.rendererClusterUrl = windowOrigin; }; - hooks.beforeEach(function () { - authenticateSession({ username: 'admin' }); + hooks.beforeEach(async function () { + await authenticateSession({ username: 'admin' }); // bypass mirage config that expects recursive to be passed in as queryParam this.server.get('/targets', ({ targets }) => targets.all()); @@ -165,7 +165,7 @@ module('Acceptance | projects | targets | index', function (hooks) { }); test('visiting index while unauthenticated redirects to global authenticate method', async function (assert) { - invalidateSession(); + await invalidateSession(); this.stubCacheDaemonSearch(); await visit(urls.targets); await a11yAudit(); diff --git a/ui/desktop/tests/acceptance/projects/targets/target-test.js b/ui/desktop/tests/acceptance/projects/targets/target-test.js index bed15c4261..91405be657 100644 --- a/ui/desktop/tests/acceptance/projects/targets/target-test.js +++ b/ui/desktop/tests/acceptance/projects/targets/target-test.js @@ -61,8 +61,8 @@ module('Acceptance | projects | targets | target', function (hooks) { clusterUrl.rendererClusterUrl = windowOrigin; }; - hooks.beforeEach(function () { - authenticateSession(); + hooks.beforeEach(async function () { + await authenticateSession(); // bypass mirage config that expects recursive to be passed in as queryParam this.server.get('/targets', ({ targets }) => targets.all()); diff --git a/ui/desktop/tests/acceptance/scopes-test.js b/ui/desktop/tests/acceptance/scopes-test.js index 167e6c91b0..0b7b67f422 100644 --- a/ui/desktop/tests/acceptance/scopes-test.js +++ b/ui/desktop/tests/acceptance/scopes-test.js @@ -80,8 +80,8 @@ module('Acceptance | scopes', function (hooks) { clusterUrl.rendererClusterUrl = windowOrigin; }; - hooks.beforeEach(function () { - authenticateSession({ username: 'admin' }); + hooks.beforeEach(async function () { + await authenticateSession({ username: 'admin' }); // bypass mirage config that expects recursive to be passed in as queryParam this.server.get('/targets', ({ targets }) => targets.all()); @@ -239,7 +239,7 @@ module('Acceptance | scopes', function (hooks) { }); test('visiting index while unauthenticated redirects to global authenticate method', async function (assert) { - invalidateSession(); + await invalidateSession(); assert.expect(2); this.stubCacheDaemonSearch(); @@ -363,7 +363,7 @@ module('Acceptance | scopes', function (hooks) { }); test('pagination is not supported - navigate to cluster url page', async function (assert) { - invalidateSession(); + await invalidateSession(); this.stubCacheDaemonSearch(); this.ipcStub.withArgs('checkOS').returns({ isWindows: true, diff --git a/ui/desktop/tests/integration/components/credentials-panel-test.js b/ui/desktop/tests/integration/components/credentials-panel-test.js index e27a365e04..d37fc4d5e2 100644 --- a/ui/desktop/tests/integration/components/credentials-panel-test.js +++ b/ui/desktop/tests/integration/components/credentials-panel-test.js @@ -40,6 +40,21 @@ module('Integration | Component | credentials-panel', function (hooks) { ], }; + const vaultCredentials = { + source: { + id: 'clvlt_4cvscMTl0N', + name: 'Credential Library 2', + description: 'Source Description', + type: 'vault', + }, + secrets: [ + { + key: 'username', + value: 'password', + }, + ], + }; + test('it renders', async function (assert) { assert.expect(1); this.set('credentials', [credential]); @@ -51,6 +66,17 @@ module('Integration | Component | credentials-panel', function (hooks) { assert.dom('.credential-name').hasText('Credential Library 1'); }); + test('it correctly shows parsed secret from vault', async function (assert) { + this.set('credentials', [vaultCredentials]); + + await render(hbs` + + `); + assert.dom('.credential-name').hasText('Credential Library 2'); + assert.dom('.secret-container').isVisible(); + assert.dom('.secret-key').hasText('username'); + }); + test('it shows code editor when toggle clicked', async function (assert) { assert.expect(1); this.set('credentials', [credential]); diff --git a/ui/desktop/tests/integration/helpers/group-by-test.js b/ui/desktop/tests/integration/helpers/group-by-test.js index 8f7dde473e..bfa6d5a045 100644 --- a/ui/desktop/tests/integration/helpers/group-by-test.js +++ b/ui/desktop/tests/integration/helpers/group-by-test.js @@ -11,7 +11,6 @@ import { hbs } from 'ember-cli-htmlbars'; module('Integration | Helper | group-by', function (hooks) { setupRenderingTest(hooks); - // TODO: Replace this with your real tests. test('it renders', async function (assert) { this.set('inputValue', [ { group: 'a', id: '1' }, diff --git a/ui/desktop/tests/unit/controllers/application-test.js b/ui/desktop/tests/unit/controllers/application-test.js index 651ba7f346..7f4b663607 100644 --- a/ui/desktop/tests/unit/controllers/application-test.js +++ b/ui/desktop/tests/unit/controllers/application-test.js @@ -22,7 +22,7 @@ module('Unit | Controller | application', function (hooks) { }; hooks.beforeEach(async function () { - authenticateSession({}); + await authenticateSession({}); controller = this.owner.lookup('controller:application'); session = this.owner.lookup('service:session'); clusterUrl = this.owner.lookup('service:cluster-url'); diff --git a/ui/desktop/tests/unit/controllers/cluster-url-test.js b/ui/desktop/tests/unit/controllers/cluster-url-test.js index 7b889735c6..5d4547c5fe 100644 --- a/ui/desktop/tests/unit/controllers/cluster-url-test.js +++ b/ui/desktop/tests/unit/controllers/cluster-url-test.js @@ -23,7 +23,7 @@ module('Unit | Controller | cluster-url', function (hooks) { }; hooks.beforeEach(async function () { - authenticateSession({}); + await authenticateSession({}); controller = this.owner.lookup('controller:cluster-url'); clusterUrl = this.owner.lookup('service:cluster-url'); setupMockIpc(this); diff --git a/ui/desktop/tests/unit/controllers/scopes/scope/authenticate-test.js b/ui/desktop/tests/unit/controllers/scopes/scope/authenticate-test.js index 97c3893abd..2a653225be 100644 --- a/ui/desktop/tests/unit/controllers/scopes/scope/authenticate-test.js +++ b/ui/desktop/tests/unit/controllers/scopes/scope/authenticate-test.js @@ -9,7 +9,6 @@ import { setupTest } from 'ember-qunit'; module('Unit | Controller | scopes/scope/authenticate', function (hooks) { setupTest(hooks); - // TODO: Replace this with your real tests. test('it exists', function (assert) { let controller = this.owner.lookup('controller:scopes/scope/authenticate'); assert.ok(controller); diff --git a/ui/desktop/tests/unit/controllers/scopes/scope/authenticate/method/index-test.js b/ui/desktop/tests/unit/controllers/scopes/scope/authenticate/method/index-test.js index 1303957be6..e3722ab153 100644 --- a/ui/desktop/tests/unit/controllers/scopes/scope/authenticate/method/index-test.js +++ b/ui/desktop/tests/unit/controllers/scopes/scope/authenticate/method/index-test.js @@ -29,7 +29,7 @@ module( }; hooks.beforeEach(async function () { - authenticateSession({}); + await authenticateSession({}); controller = this.owner.lookup( 'controller:scopes/scope/authenticate/method/index', ); diff --git a/ui/desktop/tests/unit/controllers/scopes/scope/projects/sessions/index-test.js b/ui/desktop/tests/unit/controllers/scopes/scope/projects/sessions/index-test.js index 8b6ebd430b..5650f86c95 100644 --- a/ui/desktop/tests/unit/controllers/scopes/scope/projects/sessions/index-test.js +++ b/ui/desktop/tests/unit/controllers/scopes/scope/projects/sessions/index-test.js @@ -44,7 +44,7 @@ module( }; hooks.beforeEach(async function () { - authenticateSession({}); + await authenticateSession({}); store = this.owner.lookup('service:store'); controller = this.owner.lookup( 'controller:scopes/scope/projects/sessions/index', diff --git a/ui/desktop/tests/unit/controllers/scopes/scope/projects/settings/index-test.js b/ui/desktop/tests/unit/controllers/scopes/scope/projects/settings/index-test.js index c642cd49f0..91081e3753 100644 --- a/ui/desktop/tests/unit/controllers/scopes/scope/projects/settings/index-test.js +++ b/ui/desktop/tests/unit/controllers/scopes/scope/projects/settings/index-test.js @@ -11,7 +11,6 @@ module( function (hooks) { setupTest(hooks); - // TODO: Replace this with your real tests. test('it exists', function (assert) { let controller = this.owner.lookup( 'controller:scopes/scope/projects/settings/index', diff --git a/ui/desktop/tests/unit/controllers/scopes/scope/projects/targets/index-test.js b/ui/desktop/tests/unit/controllers/scopes/scope/projects/targets/index-test.js index 6bd50c6e21..b7f0ee229d 100644 --- a/ui/desktop/tests/unit/controllers/scopes/scope/projects/targets/index-test.js +++ b/ui/desktop/tests/unit/controllers/scopes/scope/projects/targets/index-test.js @@ -44,7 +44,7 @@ module( }; hooks.beforeEach(async function () { - authenticateSession({}); + await authenticateSession({}); store = this.owner.lookup('service:store'); controller = this.owner.lookup( 'controller:scopes/scope/projects/targets/index', diff --git a/ui/desktop/tests/unit/services/client-agent-sessions-test.js b/ui/desktop/tests/unit/services/client-agent-sessions-test.js index 18771d60b3..864a93c22a 100644 --- a/ui/desktop/tests/unit/services/client-agent-sessions-test.js +++ b/ui/desktop/tests/unit/services/client-agent-sessions-test.js @@ -9,7 +9,6 @@ import { setupTest } from 'desktop/tests/helpers'; module('Unit | Service | client-agent-sessions', function (hooks) { setupTest(hooks); - // TODO: Replace this with your real tests. test('it exists', function (assert) { let service = this.owner.lookup('service:client-agent-sessions'); assert.ok(service); diff --git a/ui/desktop/tests/unit/services/session-test.js b/ui/desktop/tests/unit/services/session-test.js index 9f7b49d1b7..84dfbdfc70 100644 --- a/ui/desktop/tests/unit/services/session-test.js +++ b/ui/desktop/tests/unit/services/session-test.js @@ -9,7 +9,6 @@ import { setupTest } from 'desktop/tests/helpers'; module('Unit | Service | session', function (hooks) { setupTest(hooks); - // TODO: Replace this with your real tests. test('it exists', function (assert) { let service = this.owner.lookup('service:session'); assert.ok(service); diff --git a/ui/desktop/tests/unit/services/store-test.js b/ui/desktop/tests/unit/services/store-test.js index 680c942f55..3e6e8e8141 100644 --- a/ui/desktop/tests/unit/services/store-test.js +++ b/ui/desktop/tests/unit/services/store-test.js @@ -9,7 +9,6 @@ import { setupTest } from 'desktop/tests/helpers'; module('Unit | Service | store', function (hooks) { setupTest(hooks); - // TODO: Replace this with your real tests. test('it exists', function (assert) { let service = this.owner.lookup('service:store'); assert.ok(service); diff --git a/yarn.lock b/yarn.lock index c73d3afd70..4e1076a9f5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4735,12 +4735,12 @@ mkdirp "^1.0.4" rimraf "^3.0.2" -"@playwright/test@^1.43.0": - version "1.43.1" - resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.43.1.tgz#16728a59eb8ce0f60472f98d8886d6cab0fa3e42" - integrity sha512-HgtQzFgNEEo4TE22K/X7sYTYNqEMMTZmFS8kTq6m8hXj+m1D8TgwgIbumHddJa9h4yl4GkKb8/bgAl2+g7eDgA== +"@playwright/test@^1.49.0": + version "1.49.0" + resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.49.0.tgz#74227385b58317ee076b86b56d0e1e1b25cff01e" + integrity sha512-DMulbwQURa8rNIQrf94+jPJQ4FmOVdpE5ZppRNvWVjvhC+6sOeo28r8MgIpQRYouXRtt/FCCXU7zn20jnHR4Qw== dependencies: - playwright "1.43.1" + playwright "1.49.0" "@pnpm/constants@7.1.1": version "7.1.1" @@ -4797,13 +4797,6 @@ resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.0.1.tgz#d26729db850fa327b7cacc5522252194404226f5" integrity sha512-Qm9hBEBu18wt1PO2flE7LPb30BHMQt1eQgbV76YntdNk73XZGpn3izvGTYxbGgzXKgbCjiia0uxTd3aTNQrY/g== -"@sinonjs/commons@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-2.0.0.tgz#fd4ca5b063554307e8327b4564bd56d3b73924a3" - integrity sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg== - dependencies: - type-detect "4.0.8" - "@sinonjs/commons@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-3.0.0.tgz#beb434fe875d965265e04722ccfc21df7f755d72" @@ -4825,23 +4818,23 @@ dependencies: "@sinonjs/commons" "^3.0.0" -"@sinonjs/fake-timers@^11.2.2": - version "11.2.2" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz#50063cc3574f4a27bd8453180a04171c85cc9699" - integrity sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw== +"@sinonjs/fake-timers@^13.0.1", "@sinonjs/fake-timers@^13.0.2": + version "13.0.5" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz#36b9dbc21ad5546486ea9173d6bea063eb1717d5" + integrity sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw== dependencies: - "@sinonjs/commons" "^3.0.0" + "@sinonjs/commons" "^3.0.1" -"@sinonjs/samsam@^8.0.0": - version "8.0.0" - resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-8.0.0.tgz#0d488c91efb3fa1442e26abea81759dfc8b5ac60" - integrity sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew== +"@sinonjs/samsam@^8.0.1": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-8.0.2.tgz#e4386bf668ff36c95949e55a38dc5f5892fc2689" + integrity sha512-v46t/fwnhejRSFTGqbpn9u+LQ9xJDse10gNnPgAcxgdoCDMXj/G2asWAC/8Qs+BAZDicX+MNZouXT1A7c83kVw== dependencies: - "@sinonjs/commons" "^2.0.0" + "@sinonjs/commons" "^3.0.1" lodash.get "^4.4.2" - type-detect "^4.0.8" + type-detect "^4.1.0" -"@sinonjs/text-encoding@^0.7.2": +"@sinonjs/text-encoding@^0.7.3": version "0.7.3" resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz#282046f03e886e352b2d5f5da5eb755e01457f3f" integrity sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA== @@ -5524,6 +5517,13 @@ ansi-escapes@^4.2.1, ansi-escapes@^4.3.0, ansi-escapes@^4.3.2: dependencies: type-fest "^0.21.3" +ansi-escapes@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-7.0.0.tgz#00fc19f491bbb18e1d481b97868204f92109bfe7" + integrity sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw== + dependencies: + environment "^1.0.0" + ansi-html@^0.0.7, ansi-html@^0.0.9: version "0.0.9" resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.9.tgz#6512d02342ae2cc68131952644a129cb734cd3f0" @@ -5578,6 +5578,11 @@ ansi-styles@^6.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.1.0.tgz#87313c102b8118abd57371afab34618bf7350ed3" integrity sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ== +ansi-styles@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + ansi-styles@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.0.0.tgz#cb102df1c56f5123eab8b67cd7b98027a0279178" @@ -7020,7 +7025,7 @@ chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^5.3.0: +chalk@^5.3.0, chalk@~5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.3.0.tgz#67c20a7ebef70e7f3970a01f90fa210cb6860385" integrity sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w== @@ -7169,6 +7174,13 @@ cli-cursor@^3.1.0: dependencies: restore-cursor "^3.1.0" +cli-cursor@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-5.0.0.tgz#24a4831ecf5a6b01ddeb32fb71a4b2088b0dce38" + integrity sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw== + dependencies: + restore-cursor "^5.0.0" + cli-progress@^3.9.0: version "3.9.0" resolved "https://registry.yarnpkg.com/cli-progress/-/cli-progress-3.9.0.tgz#25db83447deb812e62d05bac1af9aec5387ef3d4" @@ -7207,13 +7219,13 @@ cli-truncate@^2.1.0: slice-ansi "^3.0.0" string-width "^4.2.0" -cli-truncate@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-3.1.0.tgz#3f23ab12535e3d73e839bb43e73c9de487db1389" - integrity sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA== +cli-truncate@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-4.0.0.tgz#6cc28a2924fee9e25ce91e973db56c7066e6172a" + integrity sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA== dependencies: slice-ansi "^5.0.0" - string-width "^5.0.0" + string-width "^7.0.0" cli-width@^2.0.0: version "2.2.1" @@ -7337,12 +7349,7 @@ colord@^2.9.3: resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43" integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== -colorette@^2.0.16, colorette@^2.0.17: - version "2.0.19" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798" - integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== - -colorette@^2.0.19: +colorette@^2.0.19, colorette@^2.0.20: version "2.0.20" resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== @@ -7387,10 +7394,10 @@ commander@^8.3.0: resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== -commander@^9.3.0: - version "9.3.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-9.3.0.tgz#f619114a5a2d2054e0d9ff1b31d5ccf89255e26b" - integrity sha512-hv95iU5uXPbK83mjrJKuZyFM/LBAoCV/XhVGkS5Je6tl7sxr6A0ITMw5WoRV46/UaJ46Nllm3Xt7IaJhXTIkzw== +commander@~12.1.0: + version "12.1.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3" + integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA== commitizen@^4.0.3: version "4.3.1" @@ -7867,6 +7874,13 @@ debug@~4.3.4: dependencies: ms "2.1.2" +debug@~4.3.6: + version "4.3.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" + integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== + dependencies: + ms "^2.1.3" + debuglog@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" @@ -8044,10 +8058,10 @@ diff@^5.1.0: resolved "https://registry.yarnpkg.com/diff/-/diff-5.1.0.tgz#bc52d298c5ea8df9194800224445ed43ffc87e40" integrity sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw== -diff@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531" - integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A== +diff@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-7.0.0.tgz#3fb34d387cd76d803f6eebea67b921dab0182a9a" + integrity sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw== dir-compare@^3.0.0: version "3.3.0" @@ -8175,11 +8189,6 @@ dotenv@^16.4.5: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== -eastasianwidth@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" - integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== - editions@^1.1.1: version "1.3.4" resolved "https://registry.yarnpkg.com/editions/-/editions-1.3.4.tgz#3662cb592347c3168eb8e498a0ff73271d67f50b" @@ -8315,14 +8324,6 @@ ember-auto-import@2.8.1, ember-auto-import@^2.2.3, ember-auto-import@^2.2.4, emb typescript-memoize "^1.0.0-alpha.3" walk-sync "^3.0.0" -ember-autofocus-modifier@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/ember-autofocus-modifier/-/ember-autofocus-modifier-4.0.1.tgz#3ebfed826b2544831fd1e4b77540142cd4d3ac2b" - integrity sha512-WhRXG3JvrwNW7O/CFgduJ9V7zqD2QI2K89BYLD8Ai18D3nLJfKDdAkizYb+noIfHone6ttZ/PxNmW8+KdfapNg== - dependencies: - ember-cli-babel "^7.26.6" - ember-modifier "^3.2.7" - ember-browser-services@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/ember-browser-services/-/ember-browser-services-4.0.3.tgz#c32b4127c2868a5668c4bfe8d8413885a613ec8c" @@ -8362,14 +8363,6 @@ ember-can@^4.2.0: ember-cli-htmlbars "^6.0.0" ember-inflector "^4.0.2" -ember-cli-app-version@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/ember-cli-app-version/-/ember-cli-app-version-6.0.1.tgz#401cdd440c7fef2059aa54bbadae9ca581d4faa0" - integrity sha512-XA1FwkWA5QytmWF0jcJqEr3jcZoiCl9Fb33TZgOVfClL7Voxe+/RwzISEprBRQgbf7j8z1xf8/RJCKfclUy3rQ== - dependencies: - ember-cli-babel "^7.26.11" - git-repo-info "^2.1.1" - ember-cli-babel-plugin-helpers@^1.0.0, ember-cli-babel-plugin-helpers@^1.1.0, ember-cli-babel-plugin-helpers@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ember-cli-babel-plugin-helpers/-/ember-cli-babel-plugin-helpers-1.1.1.tgz#5016b80cdef37036c4282eef2d863e1d73576879" @@ -9516,14 +9509,6 @@ ember-truth-helpers@^4.0.3: "@embroider/addon-shim" "^1.8.6" ember-functions-as-helper-polyfill "^2.1.2" -ember-uuid@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/ember-uuid/-/ember-uuid-2.1.0.tgz#a65d030534d47ced1ea22be9b7d3821eff71f99c" - integrity sha512-oiUNIBPCyJtzkcRUAi3qCRwU+yq5r9gzlPLKRG1pQ2WA209lphmZQRR+TyaeNwOijs9bjttzFV6YOyaTO40B/A== - dependencies: - ember-cli-babel "^7.11.1" - ember-cli-htmlbars "^4.0.0" - ember-window-mock@^0.8.1: version "0.8.1" resolved "https://registry.yarnpkg.com/ember-window-mock/-/ember-window-mock-0.8.1.tgz#1fc5db20b3d9fc3592d4ef6f83d6b5a629c79174" @@ -9545,16 +9530,16 @@ emittery@^0.13.1: resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" integrity sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ== +emoji-regex@^10.3.0: + version "10.4.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.4.0.tgz#03553afea80b3975749cfcb36f776ca268e413d4" + integrity sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw== + emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== -emoji-regex@^9.2.2: - version "9.2.2" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" - integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== - emoji-regex@~6.1.0: version "6.1.3" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.1.3.tgz#ec79a3969b02d2ecf2b72254279bf99bc7a83932" @@ -9645,6 +9630,11 @@ env-paths@^2.2.0: resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== +environment@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/environment/-/environment-1.1.0.tgz#8e86c66b180f363c7ab311787e0259665f45a9f1" + integrity sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q== + err-code@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/err-code/-/err-code-2.0.3.tgz#23c2f3b756ffdfc608d30e27c9a941024807e7f9" @@ -10262,19 +10252,19 @@ execa@^5.0.0, execa@^5.1.1: signal-exit "^3.0.3" strip-final-newline "^2.0.0" -execa@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-6.1.0.tgz#cea16dee211ff011246556388effa0818394fb20" - integrity sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA== +execa@~8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-8.0.1.tgz#51f6a5943b580f963c3ca9c6321796db8cc39b8c" + integrity sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg== dependencies: cross-spawn "^7.0.3" - get-stream "^6.0.1" - human-signals "^3.0.1" + get-stream "^8.0.1" + human-signals "^5.0.0" is-stream "^3.0.0" merge-stream "^2.0.0" npm-run-path "^5.1.0" onetime "^6.0.0" - signal-exit "^3.0.7" + signal-exit "^4.1.0" strip-final-newline "^3.0.0" exit@^0.1.2: @@ -11040,6 +11030,11 @@ get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== +get-east-asian-width@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz#21b4071ee58ed04ee0db653371b55b4299875389" + integrity sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ== + get-installed-path@^2.0.3: version "2.1.1" resolved "https://registry.yarnpkg.com/get-installed-path/-/get-installed-path-2.1.1.tgz#a1f33dc6b8af542c9331084e8edbe37fe2634152" @@ -11110,11 +11105,16 @@ get-stream@^5.0.0, get-stream@^5.1.0: dependencies: pump "^3.0.0" -get-stream@^6.0.0, get-stream@^6.0.1: +get-stream@^6.0.0: version "6.0.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== +get-stream@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-8.0.1.tgz#def9dfd71742cd7754a7761ed43749a27d02eca2" + integrity sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA== + get-symbol-description@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" @@ -11708,10 +11708,10 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== -human-signals@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-3.0.1.tgz#c740920859dafa50e5a3222da9d3bf4bb0e5eef5" - integrity sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ== +human-signals@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28" + integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ== humanize-ms@^1.2.1: version "1.2.1" @@ -12121,6 +12121,13 @@ is-fullwidth-code-point@^4.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz#fae3167c729e7463f8461ce512b080a49268aa88" integrity sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ== +is-fullwidth-code-point@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz#9609efced7c2f97da7b60145ef481c787c7ba704" + integrity sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA== + dependencies: + get-east-asian-width "^1.0.0" + is-generator-fn@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" @@ -13045,10 +13052,10 @@ license-checker@^25.0.1: spdx-satisfies "^4.0.0" treeify "^1.1.0" -lilconfig@2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.5.tgz#19e57fd06ccc3848fd1891655b5a447092225b25" - integrity sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg== +lilconfig@~3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.2.tgz#e4a7c3cb549e3a606c8dcc32e5ae1005e62c05cb" + integrity sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow== line-column@^1.0.2: version "1.0.2" @@ -13075,38 +13082,21 @@ linkify-it@^4.0.1: dependencies: uc.micro "^1.0.1" -lint-staged@^13.0.2: - version "13.0.2" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-13.0.2.tgz#35a1c57130e9ad5b1dea784972a40777ba433dd5" - integrity sha512-qQLfLTh9z34eMzfEHENC+QBskZfxjomrf+snF3xJ4BzilORbD989NLqQ00ughsF/A+PT41e87+WsMFabf9++pQ== - dependencies: - cli-truncate "^3.1.0" - colorette "^2.0.17" - commander "^9.3.0" - debug "^4.3.4" - execa "^6.1.0" - lilconfig "2.0.5" - listr2 "^4.0.5" - micromatch "^4.0.5" - normalize-path "^3.0.0" - object-inspect "^1.12.2" - pidtree "^0.6.0" - string-argv "^0.3.1" - yaml "^2.1.1" - -listr2@^4.0.5: - version "4.0.5" - resolved "https://registry.yarnpkg.com/listr2/-/listr2-4.0.5.tgz#9dcc50221583e8b4c71c43f9c7dfd0ef546b75d5" - integrity sha512-juGHV1doQdpNT3GSTs9IUN43QJb7KHdF9uqg7Vufs/tG9VTzpFphqF4pm/ICdAABGQxsyNn9CiYA3StkI6jpwA== - dependencies: - cli-truncate "^2.1.0" - colorette "^2.0.16" - log-update "^4.0.0" - p-map "^4.0.0" - rfdc "^1.3.0" - rxjs "^7.5.5" - through "^2.3.8" - wrap-ansi "^7.0.0" +lint-staged@^15.2.10: + version "15.2.10" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-15.2.10.tgz#92ac222f802ba911897dcf23671da5bb80643cd2" + integrity sha512-5dY5t743e1byO19P9I4b3x8HJwalIznL5E1FWYnU6OWw33KxNBSLAc6Cy7F2PsFEO8FKnLwjwm5hx7aMF0jzZg== + dependencies: + chalk "~5.3.0" + commander "~12.1.0" + debug "~4.3.6" + execa "~8.0.1" + lilconfig "~3.1.2" + listr2 "~8.2.4" + micromatch "~4.0.8" + pidtree "~0.6.0" + string-argv "~0.3.2" + yaml "~2.5.0" listr2@^5.0.3: version "5.0.8" @@ -13122,6 +13112,18 @@ listr2@^5.0.3: through "^2.3.8" wrap-ansi "^7.0.0" +listr2@~8.2.4: + version "8.2.5" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-8.2.5.tgz#5c9db996e1afeb05db0448196d3d5f64fec2593d" + integrity sha512-iyAZCeyD+c1gPyE9qpFu8af0Y+MRtmKOncdGoA2S5EY8iFq99dmmvkNnHiWo+pj0s7yH7l3KPIgee77tKpXPWQ== + dependencies: + cli-truncate "^4.0.0" + colorette "^2.0.20" + eventemitter3 "^5.0.1" + log-update "^6.1.0" + rfdc "^1.4.1" + wrap-ansi "^9.0.0" + livereload-js@^3.3.1: version "3.3.2" resolved "https://registry.yarnpkg.com/livereload-js/-/livereload-js-3.3.2.tgz#c88b009c6e466b15b91faa26fd7c99d620e12651" @@ -13321,6 +13323,17 @@ log-update@^4.0.0: slice-ansi "^4.0.0" wrap-ansi "^6.2.0" +log-update@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/log-update/-/log-update-6.1.0.tgz#1a04ff38166f94647ae1af562f4bd6a15b1b7cd4" + integrity sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w== + dependencies: + ansi-escapes "^7.0.0" + cli-cursor "^5.0.0" + slice-ansi "^7.1.0" + strip-ansi "^7.1.0" + wrap-ansi "^9.0.0" + longest-streak@^2.0.0: version "2.0.4" resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.4.tgz#b8599957da5b5dab64dee3fe316fa774597d90e4" @@ -13783,7 +13796,7 @@ micromark@^2.11.3, micromark@~2.11.0, micromark@~2.11.3: debug "^4.0.0" parse-entities "^2.0.0" -micromatch@^3.1.4, micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5, micromatch@^4.0.8: +micromatch@^3.1.4, micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5, micromatch@^4.0.8, micromatch@~4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== @@ -13828,6 +13841,11 @@ mimic-fn@^4.0.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== +mimic-function@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/mimic-function/-/mimic-function-5.0.1.tgz#acbe2b3349f99b9deaca7fb70e48b83e94e67076" + integrity sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA== + mimic-response@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" @@ -14044,7 +14062,7 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3, ms@^2.0.0, ms@^2.1.1: +ms@2.1.3, ms@^2.0.0, ms@^2.1.1, ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -14074,6 +14092,11 @@ nanoid@^3.3.6: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== +nanoid@^5.0.9: + version "5.0.9" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-5.0.9.tgz#977dcbaac055430ce7b1e19cf0130cea91a20e50" + integrity sha512-Aooyr6MXU6HpvvWXKoVoXwKMs/KyVakWwg7xQfv5/S/RIgJMy0Ifa45H9qqYy7pTCszrHzP21Uk4PZq2HpEM8Q== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -14104,14 +14127,14 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== -nise@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/nise/-/nise-6.0.1.tgz#582a347929828924d9e4e9c93f7549800cd0912c" - integrity sha512-DAyWGPQEuJVlL2eqKw6gdZKT+E/jo/ZrjEUDAslJLluCz81nWy+KSYybNp3KFm887Yvp7hv12jSM82ld8BmLxg== +nise@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/nise/-/nise-6.1.1.tgz#78ea93cc49be122e44cb7c8fdf597b0e8778b64a" + integrity sha512-aMSAzLVY7LyeM60gvBS423nBmIPP+Wy7St7hsb+8/fc1HmeoHJfLO8CKse4u3BtOZvQLJghYPI2i/1WZrEj5/g== dependencies: - "@sinonjs/commons" "^3.0.0" - "@sinonjs/fake-timers" "^11.2.2" - "@sinonjs/text-encoding" "^0.7.2" + "@sinonjs/commons" "^3.0.1" + "@sinonjs/fake-timers" "^13.0.1" + "@sinonjs/text-encoding" "^0.7.3" just-extend "^6.2.0" path-to-regexp "^8.1.0" @@ -14381,16 +14404,16 @@ object-inspect@^1.11.0: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg== -object-inspect@^1.12.2, object-inspect@^1.9.0: - version "1.12.2" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" - integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== - object-inspect@^1.13.1: version "1.13.2" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff" integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g== +object-inspect@^1.9.0: + version "1.12.2" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" + integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== + object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" @@ -14471,6 +14494,13 @@ onetime@^6.0.0: dependencies: mimic-fn "^4.0.0" +onetime@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-7.0.0.tgz#9f16c92d8c9ef5120e3acd9dd9957cceecc1ab60" + integrity sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ== + dependencies: + mimic-function "^5.0.0" + optionator@^0.9.1: version "0.9.1" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" @@ -14841,7 +14871,7 @@ picomatch@^2.3.1: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -pidtree@^0.6.0: +pidtree@~0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.6.0.tgz#90ad7b6d42d5841e69e0a2419ef38f8883aa057c" integrity sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g== @@ -14889,17 +14919,17 @@ pkg-up@^3.1.0: dependencies: find-up "^3.0.0" -playwright-core@1.43.1: - version "1.43.1" - resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.43.1.tgz#0eafef9994c69c02a1a3825a4343e56c99c03b02" - integrity sha512-EI36Mto2Vrx6VF7rm708qSnesVQKbxEWvPrfA1IPY6HgczBplDx7ENtx+K2n4kJ41sLLkuGfmb0ZLSSXlDhqPg== +playwright-core@1.49.0: + version "1.49.0" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.49.0.tgz#8e69ffed3f41855b854982f3632f2922c890afcb" + integrity sha512-R+3KKTQF3npy5GTiKH/T+kdhoJfJojjHESR1YEWhYuEKRVfVaxH3+4+GvXE5xyCngCxhxnykk0Vlah9v8fs3jA== -playwright@1.43.1, playwright@^1.43.0: - version "1.43.1" - resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.43.1.tgz#8ad08984ac66c9ef3d0db035be54dd7ec9f1c7d9" - integrity sha512-V7SoH0ai2kNt1Md9E3Gwas5B9m8KR2GVvwZnAI6Pg0m3sh7UvgiYhRrhsziCmqMJNouPckiOhk8T+9bSAK0VIA== +playwright@1.49.0: + version "1.49.0" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.49.0.tgz#df6b9e05423377a99658202844a294a8afb95d0a" + integrity sha512-eKpmys0UFDnfNb3vfsf8Vx2LEOtflgRebl0Im2eQQnYMA4Aqd+Zw8bEOB+7ZKvN76901mRnqdsiOGKxzVTbi7A== dependencies: - playwright-core "1.43.1" + playwright-core "1.49.0" optionalDependencies: fsevents "2.3.2" @@ -15872,6 +15902,14 @@ restore-cursor@^3.1.0: onetime "^5.1.0" signal-exit "^3.0.2" +restore-cursor@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-5.1.0.tgz#0766d95699efacb14150993f55baf0953ea1ebe7" + integrity sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA== + dependencies: + onetime "^7.0.0" + signal-exit "^4.1.0" + retry@^0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" @@ -15887,6 +15925,11 @@ rfdc@^1.3.0: resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== +rfdc@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.4.1.tgz#778f76c4fb731d93414e8f925fbecf64cce7f6ca" + integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA== + rimraf@^2.2.8, rimraf@^2.3.4, rimraf@^2.4.3, rimraf@^2.5.3, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" @@ -16287,6 +16330,11 @@ signal-exit@^4.0.1: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.0.2.tgz#ff55bb1d9ff2114c13b400688fa544ac63c36967" integrity sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q== +signal-exit@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + silent-error@^1.0.0, silent-error@^1.0.1, silent-error@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/silent-error/-/silent-error-1.1.1.tgz#f72af5b0d73682a2ba1778b7e32cd8aa7c2d8662" @@ -16299,17 +16347,17 @@ simple-html-tokenizer@^0.5.11: resolved "https://registry.yarnpkg.com/simple-html-tokenizer/-/simple-html-tokenizer-0.5.11.tgz#4c5186083c164ba22a7b477b7687ac056ad6b1d9" integrity sha512-C2WEK/Z3HoSFbYq8tI7ni3eOo/NneSPRoPpcM7WdLjFOArFuyXEjAoCdOC3DgMfRyziZQ1hCNR4mrNdWEvD0og== -sinon@^18.0.0: - version "18.0.0" - resolved "https://registry.yarnpkg.com/sinon/-/sinon-18.0.0.tgz#69ca293dbc3e82590a8b0d46c97f63ebc1e5fc01" - integrity sha512-+dXDXzD1sBO6HlmZDd7mXZCR/y5ECiEiGCBSGuFD/kZ0bDTofPYc6JaeGmPSF+1j1MejGUWkORbYOLDyvqCWpA== +sinon@^19.0.2: + version "19.0.2" + resolved "https://registry.yarnpkg.com/sinon/-/sinon-19.0.2.tgz#944cf771d22236aa84fc1ab70ce5bffc3a215dad" + integrity sha512-euuToqM+PjO4UgXeLETsfQiuoyPXlqFezr6YZDFwHR3t4qaX0fZUe1MfPMznTL5f8BWrVS89KduLdMUsxFCO6g== dependencies: "@sinonjs/commons" "^3.0.1" - "@sinonjs/fake-timers" "^11.2.2" - "@sinonjs/samsam" "^8.0.0" - diff "^5.2.0" - nise "^6.0.0" - supports-color "^7" + "@sinonjs/fake-timers" "^13.0.2" + "@sinonjs/samsam" "^8.0.1" + diff "^7.0.0" + nise "^6.1.1" + supports-color "^7.2.0" sisteransi@^1.0.5: version "1.0.5" @@ -16352,6 +16400,14 @@ slice-ansi@^5.0.0: ansi-styles "^6.0.0" is-fullwidth-code-point "^4.0.0" +slice-ansi@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-7.1.0.tgz#cd6b4655e298a8d1bdeb04250a433094b347b9a9" + integrity sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg== + dependencies: + ansi-styles "^6.2.1" + is-fullwidth-code-point "^5.0.0" + slide@~1.1.3: version "1.1.6" resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707" @@ -16622,10 +16678,10 @@ statuses@2.0.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= -string-argv@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" - integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg== +string-argv@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6" + integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q== string-length@^4.0.1: version "4.0.2" @@ -16675,14 +16731,14 @@ string-width@^4.1.0, string-width@^4.2.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" -string-width@^5.0.0: - version "5.1.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" - integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== +string-width@^7.0.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-7.2.0.tgz#b5bb8e2165ce275d4d43476dd2700ad9091db6dc" + integrity sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ== dependencies: - eastasianwidth "^0.2.0" - emoji-regex "^9.2.2" - strip-ansi "^7.0.1" + emoji-regex "^10.3.0" + get-east-asian-width "^1.0.0" + strip-ansi "^7.1.0" string.prototype.matchall@^4.0.4: version "4.0.5" @@ -16796,10 +16852,10 @@ strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" -strip-ansi@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" - integrity sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw== +strip-ansi@^7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== dependencies: ansi-regex "^6.0.1" @@ -16999,7 +17055,7 @@ supports-color@^5.3.0: dependencies: has-flag "^3.0.0" -supports-color@^7, supports-color@^7.0.0, supports-color@^7.1.0: +supports-color@^7.0.0, supports-color@^7.1.0, supports-color@^7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== @@ -17437,11 +17493,16 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" -type-detect@4.0.8, type-detect@^4.0.8: +type-detect@4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== +type-detect@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.1.0.tgz#deb2453e8f08dcae7ae98c626b13dddb0155906c" + integrity sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw== + type-fest@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" @@ -17739,16 +17800,16 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== +uuid@^11.0.3: + version "11.0.3" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.0.3.tgz#248451cac9d1a4a4128033e765d137e2b2c49a3d" + integrity sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg== + uuid@^8.3.2: version "8.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== -uuid@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5" - integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg== - v8-compile-cache@^2.0.3, v8-compile-cache@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" @@ -18083,6 +18144,15 @@ wrap-ansi@^7.0.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-9.0.0.tgz#1a3dc8b70d85eeb8398ddfb1e4a02cd186e58b3e" + integrity sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q== + dependencies: + ansi-styles "^6.2.1" + string-width "^7.0.0" + strip-ansi "^7.1.0" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -18162,10 +18232,10 @@ yaml@^1.10.0: resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== -yaml@^2.1.1: - version "2.2.2" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.2.2.tgz#ec551ef37326e6d42872dad1970300f8eb83a073" - integrity sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA== +yaml@~2.5.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.5.1.tgz#c9772aacf62cb7494a95b0c4f1fb065b563db130" + integrity sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q== yargs-parser@^20.2.9: version "20.2.9"