/** * Copyright IBM Corp. 2021, 2026 * SPDX-License-Identifier: BUSL-1.1 */ import { module, test } from 'qunit'; import { setupRenderingTest } from 'admin/tests/helpers'; import { setupIntl } from 'ember-intl/test-support'; import { setupMirage } from 'admin/tests/helpers/mirage'; import { render } from '@ember/test-helpers'; import { hbs } from 'ember-cli-htmlbars'; import * as commonSelectors from 'admin/tests/helpers/selectors'; const GRANT_ACTIONS_TABLE = '[data-test-grant-actions-table]'; const GRANT_ACTIONS_EMPTY_STATE = '[data-test-grant-actions-empty-state]'; const GRANT_ACTIONS_NO_TYPE_DETECTED = '[data-test-grant-actions-no-type-detected]'; const GRANT_ACTIONS_INVALID_ID_OR_TYPE = '[data-test-grant-actions-invalid-id-or-type]'; module('Integration | Component | grant-actions/index', function (hooks) { setupRenderingTest(hooks); setupMirage(hooks); setupIntl(hooks, 'en-us'); hooks.beforeEach(async function () { const response = await fetch('/grants-schema.json'); if (!response.ok) { throw new Error(`Failed to fetch grants schema: ${response.status}`); } const grantsSchemaData = await response.json(); this.grantsSchema = grantsSchemaData; }); test('it renders the actions relevant to the current grant line', async function (assert) { this.grantString = 'ids=hcst_1234567890;type=host-set'; await render(hbs` `); assert.dom(GRANT_ACTIONS_TABLE).isVisible(); assert.dom(commonSelectors.TABLE_ROWS).isVisible({ count: 8 }); const rowData = [ ...this.element.querySelectorAll(commonSelectors.TABLE_ROWS), ].map((row) => { const [name, description] = [...row.querySelectorAll('td')].map((cell) => cell.textContent.trim(), ); return { name, description }; }); assert.deepEqual( rowData.map(({ name }) => name), [ 'add-hosts', 'create', 'delete', 'list', 'read', 'remove-hosts', 'set-hosts', 'update', ], ); assert.deepEqual(rowData[0], { name: 'add-hosts', description: 'Add hosts to a host set', }); assert.deepEqual(rowData[1], { name: 'create', description: 'Create a new host-set', }); assert.deepEqual(rowData.at(-1), { name: 'update', description: 'Update a host-set', }); assert.dom(GRANT_ACTIONS_EMPTY_STATE).doesNotExist(); assert.dom(GRANT_ACTIONS_NO_TYPE_DETECTED).doesNotExist(); }); test('it renders relevant actions for template IDs', async function (assert) { this.grantString = 'ids={{.User.Id}}'; await render(hbs` `); assert.dom(GRANT_ACTIONS_TABLE).isVisible(); assert.dom(commonSelectors.TABLE_ROWS).isVisible({ count: 7 }); const rowData = [ ...this.element.querySelectorAll(commonSelectors.TABLE_ROWS), ].map((row) => { const [name, description] = [...row.querySelectorAll('td')].map((cell) => cell.textContent.trim(), ); return { name, description }; }); assert.deepEqual( rowData.map(({ name }) => name), [ 'add-accounts', 'delete', 'list-resolvable-aliases', 'read', 'remove-accounts', 'set-accounts', 'update', ], ); assert.deepEqual(rowData[0], { name: 'add-accounts', description: 'Add accounts to a user', }); assert.deepEqual(rowData[1], { name: 'delete', description: 'Delete a user', }); assert.deepEqual(rowData.at(-1), { name: 'update', description: 'Update a user', }); assert.dom(GRANT_ACTIONS_EMPTY_STATE).doesNotExist(); assert.dom(GRANT_ACTIONS_NO_TYPE_DETECTED).doesNotExist(); }); test('it renders the no resource type detected state when the current line has no detectable type', async function (assert) { this.grantString = 'output_fields=*'; await render(hbs` `); assert.dom(GRANT_ACTIONS_TABLE).doesNotExist(); assert.dom(GRANT_ACTIONS_INVALID_ID_OR_TYPE).doesNotExist(); assert .dom(GRANT_ACTIONS_NO_TYPE_DETECTED) .hasText('No resource type detected.'); assert.dom(GRANT_ACTIONS_EMPTY_STATE).doesNotExist(); }); test('it renders the invalid ID and type state for an unknown type', async function (assert) { this.grantString = 'type=not-a-real-type;actions='; await render(hbs` `); assert.dom(GRANT_ACTIONS_TABLE).doesNotExist(); assert.dom(GRANT_ACTIONS_NO_TYPE_DETECTED).doesNotExist(); assert.dom(GRANT_ACTIONS_EMPTY_STATE).doesNotExist(); assert .dom(GRANT_ACTIONS_INVALID_ID_OR_TYPE) .hasText('Invalid ID and type. No actions available.'); }); test('it renders the invalid ID and type state for an unknown ID', async function (assert) { this.grantString = 'ids=bad_123;actions='; await render(hbs` `); assert.dom(GRANT_ACTIONS_TABLE).doesNotExist(); assert.dom(GRANT_ACTIONS_NO_TYPE_DETECTED).doesNotExist(); assert.dom(GRANT_ACTIONS_EMPTY_STATE).doesNotExist(); assert .dom(GRANT_ACTIONS_INVALID_ID_OR_TYPE) .hasText('Invalid ID and type. No actions available.'); }); test('it keeps the no suggestions state when a type is known but no actions apply', async function (assert) { this.grantString = 'type=host;actions='; await render(hbs` `); assert.dom(GRANT_ACTIONS_TABLE).doesNotExist(); assert.dom(GRANT_ACTIONS_INVALID_ID_OR_TYPE).doesNotExist(); assert.dom(GRANT_ACTIONS_NO_TYPE_DETECTED).doesNotExist(); assert.dom(GRANT_ACTIONS_EMPTY_STATE).hasText('No suggestions'); }); test('it renders the no-type-detected state for an empty grant string', async function (assert) { this.grantString = ''; await render(hbs` `); assert.dom(GRANT_ACTIONS_TABLE).doesNotExist(); assert.dom(GRANT_ACTIONS_INVALID_ID_OR_TYPE).doesNotExist(); assert .dom(GRANT_ACTIONS_NO_TYPE_DETECTED) .hasText('No resource type detected.'); assert.dom(GRANT_ACTIONS_EMPTY_STATE).doesNotExist(); }); test('it renders the invalid state for a pinned id paired with its own type instead of a child type', async function (assert) { this.grantString = 'ids=hcst_1234567890;type=host-catalog'; await render(hbs` `); assert.dom(GRANT_ACTIONS_TABLE).doesNotExist(); assert.dom(GRANT_ACTIONS_NO_TYPE_DETECTED).doesNotExist(); assert.dom(GRANT_ACTIONS_EMPTY_STATE).doesNotExist(); assert .dom(GRANT_ACTIONS_INVALID_ID_OR_TYPE) .hasText('Invalid ID and type. No actions available.'); }); test('it renders only CRUDL actions with fallback descriptions when type is wildcard', async function (assert) { this.grantString = 'ids=*;type=*'; await render(hbs` `); assert.dom(GRANT_ACTIONS_TABLE).isVisible(); const rows = [...this.element.querySelectorAll(commonSelectors.TABLE_ROWS)]; const rowNames = rows.map((row) => row.querySelector('td')?.textContent.trim(), ); assert.deepEqual( rowNames, ['create', 'delete', 'list', 'read', 'update'], 'only CRUDL actions are shown for wildcard grants', ); const createRow = rows.find( (row) => row.querySelector('td')?.textContent.trim() === 'create', ); assert.strictEqual( createRow.querySelectorAll('td')[1].textContent.trim(), 'Creates resources', 'description falls back to generic when no resource type is detected', ); }); });