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 @@
+
\ 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 @@
+
\ 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);
+ });
+});