diff --git a/addons/api/addon/abilities/storage-bucket.js b/addons/api/addon/abilities/storage-bucket.js new file mode 100644 index 000000000..d521b8ad4 --- /dev/null +++ b/addons/api/addon/abilities/storage-bucket.js @@ -0,0 +1,41 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import ModelAbility from './model'; + +/** + * Provides abilities for storage buckets. + */ +export default class StorageBucketAbility extends ModelAbility { + // =services + + // =permissions + + /** + * Only "known" storage bucket types may be read. + * @type {boolean} + */ + get canRead() { + return !this.model.isUnknown && this.hasAuthorizedAction('read'); + } + + /** + * Storage buckets can only exist within global and org scopes. + * @type {boolean} + */ + get canCreate() { + return ( + this.hasAuthorizedCollectionAction('create') && + (this.model.isGlobal || this.model.isOrg) + ); + } + + get canList() { + return ( + this.hasAuthorizedCollectionAction('list') && + (this.model.isGlobal || this.model.isOrg) + ); + } +} diff --git a/addons/api/app/abilities/storage-bucket.js b/addons/api/app/abilities/storage-bucket.js new file mode 100644 index 000000000..ed343c8d4 --- /dev/null +++ b/addons/api/app/abilities/storage-bucket.js @@ -0,0 +1,6 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +export { default } from 'api/abilities/storage-bucket'; diff --git a/ui/admin/app/abilities/storage-bucket.js b/ui/admin/app/abilities/storage-bucket.js new file mode 100644 index 000000000..a34d0b9d3 --- /dev/null +++ b/ui/admin/app/abilities/storage-bucket.js @@ -0,0 +1,31 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import StorageBucketAbility from 'api/abilities/storage-bucket'; +import { inject as service } from '@ember/service'; + +export default class OverrideStorageBucketAbility extends StorageBucketAbility { + // =service + @service features; + + /** + * This override ensures that storage buckets may be read only if the + * session-recording feature flag is enabled. + */ + get canRead() { + return this.features.isEnabled('session-recording') ? super.canRead : false; + } + + /** + * This override ensures that storage buckets can only be presented in the global scope. + */ + get canList() { + return this.hasAuthorizedCollectionAction('list') && this.model.isGlobal; + } + + get canNavigate() { + return this.canCreate || this.canList; + } +} diff --git a/ui/admin/app/components/layout/sidebar/body/index.hbs b/ui/admin/app/components/layout/sidebar/body/index.hbs new file mode 100644 index 000000000..d905ec2e1 --- /dev/null +++ b/ui/admin/app/components/layout/sidebar/body/index.hbs @@ -0,0 +1,3 @@ +
+ {{yield}} +
\ No newline at end of file diff --git a/ui/admin/app/components/layout/sidebar/index.hbs b/ui/admin/app/components/layout/sidebar/index.hbs new file mode 100644 index 000000000..8e66b15a7 --- /dev/null +++ b/ui/admin/app/components/layout/sidebar/index.hbs @@ -0,0 +1,8 @@ +
+ {{yield + (hash + Body=(component 'layout/sidebar/body') + Sidebar=(component 'layout/sidebar/sidebar') + ) + }} +
\ No newline at end of file diff --git a/ui/admin/app/components/layout/sidebar/sidebar/index.hbs b/ui/admin/app/components/layout/sidebar/sidebar/index.hbs new file mode 100644 index 000000000..4c986569d --- /dev/null +++ b/ui/admin/app/components/layout/sidebar/sidebar/index.hbs @@ -0,0 +1,3 @@ +
+ {{yield}} +
\ No newline at end of file diff --git a/ui/admin/app/controllers/scopes/scope/storage-buckets.js b/ui/admin/app/controllers/scopes/scope/storage-buckets.js new file mode 100644 index 000000000..249913c40 --- /dev/null +++ b/ui/admin/app/controllers/scopes/scope/storage-buckets.js @@ -0,0 +1,22 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import Controller from '@ember/controller'; +import { inject as service } from '@ember/service'; + +export default class ScopesScopeStorageBucketsController extends Controller { + // =services + + @service intl; + + // = attributes + /** + * Translated breadcrumb + * @type {string} + */ + get breadCrumb() { + return this.intl.t('resources.storage-bucket.title_plural'); + } +} diff --git a/ui/admin/app/routes/scopes/scope/storage-buckets.js b/ui/admin/app/routes/scopes/scope/storage-buckets.js index 6c75be9b8..6c01670d9 100644 --- a/ui/admin/app/routes/scopes/scope/storage-buckets.js +++ b/ui/admin/app/routes/scopes/scope/storage-buckets.js @@ -5,4 +5,9 @@ import Route from '@ember/routing/route'; -export default class ScopesScopeStorageBucketsRoute extends Route {} +export default class ScopesScopeStorageBucketsRoute extends Route { + model() { + // Temporary placeholder + return [{ scope_id: 'sb_123456' }]; + } +} diff --git a/ui/admin/app/routes/scopes/scope/storage-buckets/index.js b/ui/admin/app/routes/scopes/scope/storage-buckets/index.js index 57bbf4b1f..1796e2de3 100644 --- a/ui/admin/app/routes/scopes/scope/storage-buckets/index.js +++ b/ui/admin/app/routes/scopes/scope/storage-buckets/index.js @@ -5,4 +5,12 @@ import Route from '@ember/routing/route'; -export default class ScopesScopeStorageBucketsIndexRoute extends Route {} +export default class ScopesScopeStorageBucketsIndexRoute extends Route { + // =methods + + setupController(controller) { + const scope = this.modelFor('scopes.scope'); + super.setupController(...arguments); + controller.setProperties({ scope }); + } +} diff --git a/ui/admin/app/styles/app.scss b/ui/admin/app/styles/app.scss index c0859d17b..b3d9f1124 100644 --- a/ui/admin/app/styles/app.scss +++ b/ui/admin/app/styles/app.scss @@ -750,3 +750,28 @@ } } } +// Layout - Sidebar +.layout-sidebar { + display: grid; + grid-template-areas: 'body body sidebar'; + grid-template-columns: repeat(3, 1fr); + grid-template-rows: auto 1fr; + gap: 2rem; + + .layout-sidebar-body { + grid-area: body; + } + + .layout-sidebar-sidebar { + grid-area: sidebar; + } +} + +@media (max-width: media.width(large)) { + .layout-sidebar { + grid-template-columns: 1fr; + grid-template-areas: + 'body' + 'sidebar'; + } +} diff --git a/ui/admin/app/templates/scopes/scope.hbs b/ui/admin/app/templates/scopes/scope.hbs index 7d9a2836d..22be84374 100644 --- a/ui/admin/app/templates/scopes/scope.hbs +++ b/ui/admin/app/templates/scopes/scope.hbs @@ -32,13 +32,15 @@ {{t 'resources.session-recording.title_plural'}} - - - {{t 'resources.storage-bucket.title_plural'}} - + {{#if (can 'navigate model' @model collection='storage-buckets')}} + + + {{t 'resources.storage-bucket.title_plural'}} + + {{/if}} {{/if}} {{#if (and diff --git a/ui/admin/app/templates/scopes/scope/session-recordings/session-recording/index.hbs b/ui/admin/app/templates/scopes/scope/session-recordings/session-recording/index.hbs index 981e709ba..4bdc99323 100644 --- a/ui/admin/app/templates/scopes/scope/session-recordings/session-recording/index.hbs +++ b/ui/admin/app/templates/scopes/scope/session-recordings/session-recording/index.hbs @@ -4,16 +4,29 @@ -

{{t 'resources.session-recording.title'}} - {{@model.session_recording_id}}

-

{{t 'resources.session-recording.channel.title'}} - {{@model.channel_connection_id}}

+ + +

+ {{t 'resources.session-recording.title'}} + {{@model.session_recording_id}} +

- - {{@model.channel_connection_id}} - +

+ {{t 'resources.session-recording.channel.title'}} + {{@model.channel_connection_id}} +

+ + + {{@model.channel_connection_id}} + +
+ + +

{{t 'actions.help'}}

+
+
\ No newline at end of file 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 3faf92ead..ead6a3c53 100644 --- a/ui/admin/app/templates/scopes/scope/storage-buckets/index.hbs +++ b/ui/admin/app/templates/scopes/scope/storage-buckets/index.hbs @@ -1,4 +1,32 @@ {{! Copyright (c) HashiCorp, Inc. SPDX-License-Identifier: MPL-2.0 -}} \ No newline at end of file +}} + + + + + + + + {{! TODO: Add valid doc link and description}} +

+ {{t 'resources.storage-bucket.title_plural'}} + +

+
+ + + {{! TODO: Check can create ability using correct file }} + {{#if (can 'create model' this.scope collection='storage-buckets')}} + {{#if @model}} + + {{/if}} + {{/if}} + + + +
\ No newline at end of file diff --git a/ui/admin/config/environment.js b/ui/admin/config/environment.js index 24ebf82ec..7cb123ec6 100644 --- a/ui/admin/config/environment.js +++ b/ui/admin/config/environment.js @@ -117,6 +117,7 @@ module.exports = function (environment) { worker: '/workers', 'worker.manage-workers': '/workers/manage-workers-on-hcp', 'worker-filters': '/worker-filters', + 'storage-bucket': '/storage-buckets', }, }, diff --git a/ui/admin/tests/acceptance/storage-buckets/list-test.js b/ui/admin/tests/acceptance/storage-buckets/list-test.js new file mode 100644 index 000000000..3821a4631 --- /dev/null +++ b/ui/admin/tests/acceptance/storage-buckets/list-test.js @@ -0,0 +1,139 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import { module, test } from '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 } from 'ember-simple-auth/test-support'; + +module('Acceptance | storage-buckets | list', function (hooks) { + setupApplicationTest(hooks); + setupMirage(hooks); + + let features; + const STORAGE_BUCKET_LINK = 'Storage Buckets'; + + const instances = { + scopes: { + global: null, + org: null, + }, + }; + + const urls = { + globalScope: null, + storageBuckets: null, + }; + + hooks.beforeEach(function () { + instances.scopes.global = this.server.create('scope', { id: 'global' }); + instances.scopes.org = this.server.create('scope', { + type: 'org', + scope: { id: 'global', type: 'global' }, + }); + urls.globalScope = `/scopes/global`; + urls.storageBuckets = `${urls.globalScope}/storage-buckets`; + + features = this.owner.lookup('service:features'); + features.enable('session-recording'); + authenticateSession({}); + }); + + test('users can navigate to storage-buckets with proper authorization', async function (assert) { + assert.expect(3); + + await visit(urls.globalScope); + + assert.true( + instances.scopes.global.authorized_collection_actions[ + 'storage-buckets' + ].includes('list') + ); + assert.true( + instances.scopes.global.authorized_collection_actions[ + 'storage-buckets' + ].includes('create') + ); + assert.dom(`[href="${urls.storageBuckets}"]`).exists(); + }); + + test('user cannot navigate to index without either list or create actions', async function (assert) { + assert.expect(3); + instances.scopes.global.authorized_collection_actions['storage-buckets'] = + []; + + await visit(urls.globalScope); + + assert.false( + instances.scopes.global.authorized_collection_actions[ + 'storage-buckets' + ].includes('list') + ); + assert.false( + instances.scopes.global.authorized_collection_actions[ + 'storage-buckets' + ].includes('create') + ); + assert + .dom('[title="General"] a:nth-of-type(3)') + .doesNotIncludeText(STORAGE_BUCKET_LINK); + }); + + test('user can navigate to index with only create action', async function (assert) { + assert.expect(3); + instances.scopes.global.authorized_collection_actions['storage-buckets'] = + instances.scopes.global.authorized_collection_actions[ + 'storage-buckets' + ].filter((item) => item !== 'list'); + + await visit(urls.globalScope); + + assert.false( + instances.scopes.global.authorized_collection_actions[ + 'storage-buckets' + ].includes('list') + ); + assert.true( + instances.scopes.global.authorized_collection_actions[ + 'storage-buckets' + ].includes('create') + ); + assert.dom(`[href="${urls.storageBuckets}"]`).exists(); + }); + + test('user can navigate to index with only list action', async function (assert) { + assert.expect(3); + instances.scopes.global.authorized_collection_actions['storage-buckets'] = + instances.scopes.global.authorized_collection_actions[ + 'storage-buckets' + ].filter((item) => item !== 'create'); + + await visit(urls.globalScope); + + assert.true( + instances.scopes.global.authorized_collection_actions[ + 'storage-buckets' + ].includes('list') + ); + assert.false( + instances.scopes.global.authorized_collection_actions[ + 'storage-buckets' + ].includes('create') + ); + assert.dom(`[href="${urls.storageBuckets}"]`).exists(); + }); + + test('user cannot navigate to index when feature is disabled', async function (assert) { + assert.expect(1); + features.disable('session-recording'); + + await visit(urls.globalScope); + + assert + .dom('[title="General"] a:nth-of-type(2)') + .doesNotIncludeText(STORAGE_BUCKET_LINK); + }); +}); diff --git a/ui/admin/tests/acceptance/storage-buckets/read-test.js b/ui/admin/tests/acceptance/storage-buckets/read-test.js new file mode 100644 index 000000000..e734e79f7 --- /dev/null +++ b/ui/admin/tests/acceptance/storage-buckets/read-test.js @@ -0,0 +1,57 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import { module, test } from 'qunit'; +import { visit, currentURL, click } 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'; + +module('Acceptance | storage-buckets | read', function (hooks) { + setupApplicationTest(hooks); + setupMirage(hooks); + + let features; + + const instances = { + scopes: { + global: null, + org: null, + }, + }; + + const urls = { + globalScope: null, + storageBuckets: null, + }; + + hooks.beforeEach(function () { + instances.scopes.global = this.server.create('scope', { id: 'global' }); + instances.scopes.org = this.server.create('scope', { + type: 'org', + scope: { id: 'global', type: 'global' }, + }); + instances.storageBucket = this.server.create('storage-bucket', { + scope: instances.scopes.global, + }); + urls.globalScope = `/scopes/global`; + urls.storageBuckets = `${urls.globalScope}/storage-buckets`; + + features = this.owner.lookup('service:features'); + features.enable('session-recording'); + authenticateSession({}); + }); + + test('visiting storage buckets', async function (assert) { + assert.expect(1); + await visit(urls.globalScope); + await a11yAudit(); + + await click(`[href="${urls.storageBuckets}"]`); + + assert.strictEqual(currentURL(), urls.storageBuckets); + }); +}); diff --git a/ui/admin/tests/integration/components/layout/sidebar-test.js b/ui/admin/tests/integration/components/layout/sidebar-test.js new file mode 100644 index 000000000..95a93d6d5 --- /dev/null +++ b/ui/admin/tests/integration/components/layout/sidebar-test.js @@ -0,0 +1,23 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'admin/tests/helpers'; +import { render } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; + +module('Integration | Component | layout/sidebar', function (hooks) { + setupRenderingTest(hooks); + + test('it renders', async function (assert) { + await render(hbs` + + Body content + Sidebar content + + `); + + assert.dom('.layout-sidebar').isVisible(); + assert.dom('.layout-sidebar-body').isVisible(); + assert.dom('.layout-sidebar-body').hasText('Body content'); + assert.dom('.layout-sidebar-sidebar').isVisible(); + assert.dom('.layout-sidebar-sidebar').hasText('Sidebar content'); + }); +}); diff --git a/ui/admin/tests/integration/components/layout/sidebar/body-test.js b/ui/admin/tests/integration/components/layout/sidebar/body-test.js new file mode 100644 index 000000000..837a29e59 --- /dev/null +++ b/ui/admin/tests/integration/components/layout/sidebar/body-test.js @@ -0,0 +1,18 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'admin/tests/helpers'; +import { render } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; + +module('Integration | Component | layout/sidebar/body', function (hooks) { + setupRenderingTest(hooks); + + test('it renders', async function (assert) { + await render(hbs` + + template block text + + `); + + assert.dom(this.element).hasText('template block text'); + }); +}); diff --git a/ui/admin/tests/integration/components/layout/sidebar/sidebar-test.js b/ui/admin/tests/integration/components/layout/sidebar/sidebar-test.js new file mode 100644 index 000000000..ec42c3488 --- /dev/null +++ b/ui/admin/tests/integration/components/layout/sidebar/sidebar-test.js @@ -0,0 +1,18 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'admin/tests/helpers'; +import { render } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; + +module('Integration | Component | layout/sidebar/sidebar', function (hooks) { + setupRenderingTest(hooks); + + test('it renders', async function (assert) { + await render(hbs` + + template block text + + `); + + assert.dom(this.element).hasText('template block text'); + }); +}); diff --git a/ui/admin/tests/unit/abilities/storage-bucket-test.js b/ui/admin/tests/unit/abilities/storage-bucket-test.js new file mode 100644 index 000000000..3961c60dd --- /dev/null +++ b/ui/admin/tests/unit/abilities/storage-bucket-test.js @@ -0,0 +1,224 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; +import { + TYPE_STORAGE_BUCKET_PLUGIN, + TYPE_STORAGE_BUCKET_PLUGIN_AWS_S3, +} from 'api/models/storage-bucket'; + +module('Unit | Abilities | storage-bucket', function (hooks) { + setupTest(hooks); + + let features; + + hooks.beforeEach(function () { + features = this.owner.lookup('service:features'); + features.enable('session-recording'); + }); + + test('can read storage bucket when authorized and feature is enabled', function (assert) { + assert.expect(1); + const canService = this.owner.lookup('service:can'); + const store = this.owner.lookup('service:store'); + + const storageBucket = store.createRecord('storage-bucket', { + authorized_actions: ['read'], + type: TYPE_STORAGE_BUCKET_PLUGIN, + compositeType: TYPE_STORAGE_BUCKET_PLUGIN_AWS_S3, + }); + + assert.true(canService.can('read storage-bucket', storageBucket)); + }); + + test('cannot read storage bucket when unauthorized and feature is enabled', function (assert) { + assert.expect(1); + const canService = this.owner.lookup('service:can'); + const store = this.owner.lookup('service:store'); + + const storageBucket = store.createRecord('storage-bucket', { + authorized_actions: [], + type: TYPE_STORAGE_BUCKET_PLUGIN, + compositeType: TYPE_STORAGE_BUCKET_PLUGIN_AWS_S3, + }); + + assert.false(canService.can('read storage-bucket', storageBucket)); + }); + + test('cannot read storage bucket when authorized and feature is disabled', function (assert) { + assert.expect(1); + const canService = this.owner.lookup('service:can'); + const store = this.owner.lookup('service:store'); + features.disable('session-recording'); + + const storageBucket = store.createRecord('storage-bucket', { + authorized_actions: ['read'], + type: TYPE_STORAGE_BUCKET_PLUGIN, + compositeType: TYPE_STORAGE_BUCKET_PLUGIN_AWS_S3, + }); + + assert.false(canService.can('read storage-bucket', storageBucket)); + }); + + test('cannot read storage bucket when unauthorized and feature is disabled', function (assert) { + assert.expect(1); + const canService = this.owner.lookup('service:can'); + const store = this.owner.lookup('service:store'); + features.disable('session-recording'); + + const storageBucket = store.createRecord('storage-bucket', { + authorized_actions: [], + type: TYPE_STORAGE_BUCKET_PLUGIN, + compositeType: TYPE_STORAGE_BUCKET_PLUGIN_AWS_S3, + }); + + assert.false(canService.can('read storage-bucket', storageBucket)); + }); + + test('can list storage bucket when authorized and in global scope', function (assert) { + assert.expect(1); + const canService = this.owner.lookup('service:can'); + const store = this.owner.lookup('service:store'); + + const scopeModel = store.createRecord('scope', { + authorized_collection_actions: { 'storage-buckets': ['list'] }, + type: 'global', + id: 'global', + }); + + assert.true( + canService.can('list storage-bucket', scopeModel, { + collection: 'storage-buckets', + }) + ); + }); + + test('cannot list storage bucket when unauthorized and in global scope', function (assert) { + assert.expect(1); + const canService = this.owner.lookup('service:can'); + const store = this.owner.lookup('service:store'); + + const scopeModel = store.createRecord('scope', { + authorized_collection_actions: { 'storage-buckets': [] }, + type: 'global', + id: 'global', + }); + + assert.false( + canService.can('list storage-bucket', scopeModel, { + collection: 'storage-buckets', + }) + ); + }); + + test('cannot list storage bucket when authorized and in org scope', function (assert) { + assert.expect(1); + const canService = this.owner.lookup('service:can'); + const store = this.owner.lookup('service:store'); + + const scopeModel = store.createRecord('scope', { + authorized_collection_actions: { 'storage-buckets': ['list'] }, + type: 'org', + id: 'o_123', + }); + + assert.false( + canService.can('list storage-bucket', scopeModel, { + collection: 'storage-buckets', + }) + ); + }); + + test('can create storage bucket when authorized and in global scope', function (assert) { + assert.expect(1); + const canService = this.owner.lookup('service:can'); + const store = this.owner.lookup('service:store'); + + const scopeModel = store.createRecord('scope', { + authorized_collection_actions: { 'storage-buckets': ['create'] }, + type: 'global', + id: 'global', + }); + + assert.true( + canService.can('create storage-bucket', scopeModel, { + collection: 'storage-buckets', + }) + ); + }); + + test('can create storage bucket when authorized and in org scope', function (assert) { + assert.expect(1); + const canService = this.owner.lookup('service:can'); + const store = this.owner.lookup('service:store'); + + const scopeModel = store.createRecord('scope', { + authorized_collection_actions: { 'storage-buckets': ['create'] }, + type: 'org', + id: 'o_123', + }); + + assert.true( + canService.can('create storage-bucket', scopeModel, { + collection: 'storage-buckets', + }) + ); + }); + + test('cannot create storage bucket when authorized and in project scope', function (assert) { + assert.expect(1); + const canService = this.owner.lookup('service:can'); + const store = this.owner.lookup('service:store'); + + const scopeModel = store.createRecord('scope', { + authorized_collection_actions: { 'storage-buckets': ['create'] }, + type: 'project', + id: 'p_123', + }); + + assert.false( + canService.can('create storage-bucket', scopeModel, { + collection: 'storage-buckets', + }) + ); + }); + + test('cannot create storage bucket when unauthorized', function (assert) { + assert.expect(1); + const canService = this.owner.lookup('service:can'); + const store = this.owner.lookup('service:store'); + + const scopeModel = store.createRecord('scope', { + authorized_collection_actions: { 'storage-buckets': [] }, + type: 'global', + id: 'global', + }); + + assert.false( + canService.can('create storage-bucket', scopeModel, { + collection: 'storage-buckets', + }) + ); + }); + + test('can navigate to storage bucket when authorized', function (assert) { + assert.expect(1); + const canService = this.owner.lookup('service:can'); + const store = this.owner.lookup('service:store'); + + const scopeModel = store.createRecord('scope', { + authorized_collection_actions: { 'storage-buckets': ['create'] }, + type: 'global', + id: 'global', + }); + + assert.true( + canService.can('navigate storage-bucket', scopeModel, { + collection: 'storage-buckets', + }) + ); + }); +}); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/storage-buckets-test.js b/ui/admin/tests/unit/controllers/scopes/scope/storage-buckets-test.js new file mode 100644 index 000000000..fa69f7929 --- /dev/null +++ b/ui/admin/tests/unit/controllers/scopes/scope/storage-buckets-test.js @@ -0,0 +1,18 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import { module, test } from 'qunit'; +import { setupTest } from 'admin/tests/helpers'; + +module('Unit | Controller | scopes/scope/storage-buckets', function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let controller = this.owner.lookup( + 'controller:scopes/scope/storage-buckets' + ); + assert.ok(controller); + }); +});