mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-29 17:52:32 +00:00
UI Hide Secrets Sync from nav if not on license and/or no policy permissions (#27262)
* intial changes, haven't tested client counts or done test coverage * client count rename getter to clairfy * fix has-permission api-paths * wip * wip * fix: explicitly refresh vault.cluster model to re-fetch activatedFeatures after actication * tests: fix # of assertions for verifying that activation was called * tests: tidy overview-test * add additional api permission path and move fetch back to application * add test coverage for the service * cleanup * remove test that checked for upsell without license or on community * small comment change * welp missed component getter * flaky test fix * flaky test * small nit changes from pr reviews * add defaults to sync mirage handler * Gate sync overview route for users without access (#27320) * routes: add redirect if user does not have access to sync * tests: verify redirect on sync overview page happens * tests: organize tests modules to ensure enterprise is explicitly set up * add type enterprise required now because we do a check for this first * fix oss test --------- Co-authored-by: Noelle Daley <noelledaley@users.noreply.github.com>
This commit is contained in:
3
changelog/27262.txt
Normal file
3
changelog/27262.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
```release-note:improvement
|
||||
ui/secrets-sync: Hide Secrets Sync from the sidebar nav if user does not have access to the feature.
|
||||
```
|
||||
@@ -15,7 +15,7 @@
|
||||
Entity/Non-entity clients
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{#if @showSecretsSync}}
|
||||
{{#if @showSecretsSyncClientCounts}}
|
||||
<li>
|
||||
<LinkTo @route="vault.cluster.clients.counts.sync" data-test-tab="sync">
|
||||
Secrets sync clients
|
||||
|
||||
@@ -151,7 +151,7 @@
|
||||
</Hds::Alert>
|
||||
{{/if}}
|
||||
|
||||
<Clients::Counts::NavBar @showSecretsSync={{this.showSecretsSync}} />
|
||||
<Clients::Counts::NavBar @showSecretsSyncClientCounts={{or this.hasSecretsSyncClients this.flags.showSecretsSync}} />
|
||||
|
||||
{{! CLIENT COUNT PAGE COMPONENTS RENDER HERE }}
|
||||
{{yield}}
|
||||
|
||||
@@ -167,20 +167,10 @@ export default class ClientsCountsPageComponent extends Component<Args> {
|
||||
return activity?.total;
|
||||
}
|
||||
|
||||
get showSecretsSync(): boolean {
|
||||
get hasSecretsSyncClients(): boolean {
|
||||
const { activity } = this.args;
|
||||
// if there is any sync client data, show it
|
||||
if (activity && activity?.total?.secret_syncs > 0) return true;
|
||||
|
||||
// otherwise, show the tab based on the cluster type and license
|
||||
if (this.version.isCommunity) return false;
|
||||
|
||||
const isHvd = this.flags.isHvdManaged;
|
||||
const onLicense = this.version.hasSecretsSync;
|
||||
|
||||
// we can't tell if HVD clusters have the feature or not, so we show it by default
|
||||
// if the cluster is not HVD, show the tab if the feature is on the license
|
||||
return isHvd || onLicense;
|
||||
return activity && activity?.total?.secret_syncs > 0;
|
||||
}
|
||||
|
||||
@action
|
||||
|
||||
@@ -13,12 +13,14 @@
|
||||
@text="Secrets Engines"
|
||||
data-test-sidebar-nav-link="Secrets Engines"
|
||||
/>
|
||||
<Nav.Link
|
||||
@route="vault.cluster.sync"
|
||||
@text="Secrets Sync"
|
||||
@badge={{this.badgeText}}
|
||||
data-test-sidebar-nav-link="Secrets Sync"
|
||||
/>
|
||||
{{#if this.flags.showSecretsSync}}
|
||||
<Nav.Link
|
||||
@route="vault.cluster.sync"
|
||||
@text="Secrets Sync"
|
||||
@badge={{if this.flags.isHvdManaged "Plus" ""}}
|
||||
data-test-sidebar-nav-link="Secrets Sync"
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if (has-permission "access")}}
|
||||
<Nav.Link
|
||||
@route={{get (route-params-for "access") "route"}}
|
||||
|
||||
@@ -21,16 +21,4 @@ export default class SidebarNavClusterComponent extends Component {
|
||||
// should only return true if we're in the true root namespace
|
||||
return this.namespace.inRootNamespace && !this.cluster?.hasChrootNamespace;
|
||||
}
|
||||
|
||||
get badgeText() {
|
||||
const isHvdManaged = this.flags.isHvdManaged;
|
||||
const onLicense = this.version.hasSecretsSync;
|
||||
const isEnterprise = this.version.isEnterprise;
|
||||
|
||||
if (isHvdManaged) return 'Plus';
|
||||
if (isEnterprise && !onLicense) return 'Premium';
|
||||
if (!isEnterprise) return 'Enterprise';
|
||||
// no badge for Enterprise clusters with Secrets Sync on their license--the only remaining option.
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,10 @@ export default Route.extend(ModelBoundaryRoute, ClusterRoute, {
|
||||
'Cannot use VAULT_CLOUD_ADMIN_NAMESPACE flag with non-enterprise Vault version',
|
||||
!(managedRoot && this.version.isCommunity)
|
||||
);
|
||||
|
||||
// activatedFlags are called this high in routing to return a response used to show/hide Secrets sync on sidebar nav.
|
||||
await this.flagsService.fetchActivatedFlags();
|
||||
|
||||
if (!namespace && currentTokenName && !Ember.testing) {
|
||||
// if no namespace queryParam and user authenticated,
|
||||
// use user's root namespace to redirect to properly param'd url
|
||||
|
||||
@@ -10,6 +10,7 @@ import { DEBUG } from '@glimmer/env';
|
||||
import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities';
|
||||
import type StoreService from 'vault/services/store';
|
||||
import type VersionService from 'vault/services/version';
|
||||
import type PermissionsService from 'vault/services/permissions';
|
||||
|
||||
const FLAGS = {
|
||||
vaultCloudNamespace: 'VAULT_CLOUD_ADMIN_NAMESPACE',
|
||||
@@ -24,6 +25,7 @@ const FLAGS = {
|
||||
export default class flagsService extends Service {
|
||||
@service declare readonly version: VersionService;
|
||||
@service declare readonly store: StoreService;
|
||||
@service declare readonly permissions: PermissionsService;
|
||||
|
||||
@tracked activatedFlags: string[] = [];
|
||||
@tracked featureFlags: string[] = [];
|
||||
@@ -60,9 +62,9 @@ export default class flagsService extends Service {
|
||||
}
|
||||
|
||||
getActivatedFlags = keepLatestTask(async () => {
|
||||
if (this.version.isCommunity) return;
|
||||
// Response could change between user sessions.
|
||||
// Fire off endpoint without checking if activated features are already set.
|
||||
if (this.version.isCommunity) return;
|
||||
try {
|
||||
const response = await this.store
|
||||
.adapterFor('application')
|
||||
@@ -86,4 +88,21 @@ export default class flagsService extends Service {
|
||||
this.secretsSyncActivatePath.get('canUpdate') !== false
|
||||
);
|
||||
}
|
||||
|
||||
get showSecretsSync() {
|
||||
const isHvdManaged = this.isHvdManaged;
|
||||
const onLicense = this.version.hasSecretsSync;
|
||||
const isEnterprise = this.version.isEnterprise;
|
||||
const isActivated = this.secretsSyncIsActivated;
|
||||
|
||||
if (!isEnterprise) return false;
|
||||
if (isHvdManaged) return true;
|
||||
if (isEnterprise && !onLicense) return false;
|
||||
if (isActivated) {
|
||||
// if the feature is activated but the user does not have permissions on the `sys/sync` endpoint, hide navigation link.
|
||||
return this.permissions.hasNavPermission('sync');
|
||||
}
|
||||
// only remaining option is Enterprise with Secrets Sync on the license but the feature is not activated. In this case, we want to show the upsell page and message about either activating or having an admin activate.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,12 @@ const API_PATHS = {
|
||||
settings: {
|
||||
customMessages: 'sys/config/ui/custom-messages',
|
||||
},
|
||||
sync: {
|
||||
destinations: 'sys/sync/destinations',
|
||||
associations: 'sys/sync/associations',
|
||||
config: 'sys/sync/config',
|
||||
github: 'sys/sync/github-apps',
|
||||
},
|
||||
};
|
||||
|
||||
const API_PATHS_TO_ROUTE_PARAMS = {
|
||||
|
||||
@@ -43,7 +43,7 @@ export default class SyncActivationModal extends Component<Args> {
|
||||
.adapterFor('application')
|
||||
.ajax('/v1/sys/activation-flags/secrets-sync/activate', 'POST', { namespace });
|
||||
// must refresh and not transition because transition does not refresh the model from within a namespace
|
||||
yield this.router.refresh();
|
||||
yield this.router.refresh('vault.cluster');
|
||||
} catch (error) {
|
||||
this.args.onError(errorMessage(error));
|
||||
this.flashMessages.danger(`Error enabling feature \n ${errorMessage(error)}`);
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
<Icon @name={{@icon}} @size="24" />
|
||||
{{/if}}
|
||||
{{@title}}
|
||||
{{#if this.badgeText}}
|
||||
<Hds::Badge @text={{this.badgeText}} @color="highlight" @size="large" />
|
||||
{{#if this.flags.isHvdManaged}}
|
||||
<Hds::Badge @text="Plus feature" @color="highlight" @size="large" />
|
||||
{{/if}}
|
||||
</h1>
|
||||
</p.levelLeft>
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { service } from '@ember/service';
|
||||
|
||||
import type VersionService from 'vault/services/version';
|
||||
import type FlagsService from 'vault/services/flags';
|
||||
import type { Breadcrumb } from 'vault/vault/app-types';
|
||||
|
||||
@@ -17,18 +16,5 @@ interface Args {
|
||||
}
|
||||
|
||||
export default class SyncHeaderComponent extends Component<Args> {
|
||||
@service declare readonly version: VersionService;
|
||||
@service declare readonly flags: FlagsService;
|
||||
|
||||
get badgeText() {
|
||||
const isHvdManaged = this.flags.isHvdManaged;
|
||||
const onLicense = this.version.hasSecretsSync;
|
||||
const isEnterprise = this.version.isEnterprise;
|
||||
|
||||
if (isHvdManaged) return 'Plus feature';
|
||||
if (isEnterprise && !onLicense) return 'Premium feature';
|
||||
if (!isEnterprise) return 'Enterprise feature';
|
||||
// no badge for Enterprise clusters with Secrets Sync on their license--the only remaining option.
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,13 +13,8 @@ export default class SyncSecretsRoute extends Route {
|
||||
@service declare readonly router: RouterService;
|
||||
@service declare readonly flags: FlagService;
|
||||
|
||||
beforeModel() {
|
||||
return this.flags.fetchActivatedFlags();
|
||||
}
|
||||
|
||||
model() {
|
||||
return {
|
||||
// TODO will modify when we use the persona service.
|
||||
activatedFeatures: this.flags.activatedFlags,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -8,10 +8,12 @@ import { service } from '@ember/service';
|
||||
import { hash } from 'rsvp';
|
||||
|
||||
import type FlagsService from 'vault/services/flags';
|
||||
import type RouterService from '@ember/routing/router-service';
|
||||
import type StoreService from 'vault/services/store';
|
||||
import type VersionService from 'vault/services/version';
|
||||
|
||||
export default class SyncSecretsOverviewRoute extends Route {
|
||||
@service declare readonly router: RouterService;
|
||||
@service declare readonly store: StoreService;
|
||||
@service declare readonly flags: FlagsService;
|
||||
@service declare readonly version: VersionService;
|
||||
@@ -34,4 +36,10 @@ export default class SyncSecretsOverviewRoute extends Route {
|
||||
: [],
|
||||
});
|
||||
}
|
||||
|
||||
redirect() {
|
||||
if (!this.flags.showSecretsSync) {
|
||||
this.router.replaceWith('vault.cluster.dashboard');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Response } from 'miragejs';
|
||||
import { camelize } from '@ember/string';
|
||||
import { findDestination } from 'core/helpers/sync-destinations';
|
||||
import clientsHandler from './clients';
|
||||
import modifyPassthroughResponse from '../helpers/modify-passthrough-response';
|
||||
|
||||
export const associationsResponse = (schema, req) => {
|
||||
const { type, name } = req.params;
|
||||
@@ -116,7 +117,9 @@ const createOrUpdateDestination = (schema, req) => {
|
||||
};
|
||||
|
||||
export default function (server) {
|
||||
// default to activated
|
||||
// default to enterprise with Secrets Sync on the license and activated
|
||||
server.get('sys/health', (schema, req) => modifyPassthroughResponse(req, { enterprise: true }));
|
||||
server.get('/sys/license/features', () => ({ features: ['Secrets Sync'] }));
|
||||
server.get('/sys/activation-flags', () => {
|
||||
return {
|
||||
data: {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
// passthrough request and modify response from server
|
||||
// pass object as second arg of properties in response to override
|
||||
// ex: server.get('sys/health', (schema, req) => modifyPassthroughResponse(req, { enterprise: true }));
|
||||
export default function (req, props = {}) {
|
||||
return new Promise((resolve) => {
|
||||
const xhr = req.passthrough();
|
||||
|
||||
@@ -236,7 +236,7 @@ module('Acceptance | clients | overview | sync in license, activated', function
|
||||
});
|
||||
|
||||
test('it should render the correct tabs', async function (assert) {
|
||||
assert.dom(GENERAL.tab('sync')).exists();
|
||||
assert.dom(GENERAL.tab('sync')).exists('shows the sync tab');
|
||||
});
|
||||
|
||||
test('it should show secrets sync stats', async function (assert) {
|
||||
|
||||
@@ -22,6 +22,8 @@ module('Acceptance | clients | sync', function (hooks) {
|
||||
hooks.beforeEach(async function () {
|
||||
sinon.replace(timestamp, 'now', sinon.fake.returns(STATIC_NOW));
|
||||
syncHandler(this.server);
|
||||
const version = this.owner.lookup('service:version');
|
||||
version.type = 'enterprise';
|
||||
await authPage.login();
|
||||
return visit('/vault/clients/counts/sync');
|
||||
});
|
||||
@@ -34,22 +36,31 @@ module('Acceptance | clients | sync', function (hooks) {
|
||||
});
|
||||
});
|
||||
|
||||
module('sync not activated', function (hooks) {
|
||||
module('sync not activated and on license', function (hooks) {
|
||||
hooks.beforeEach(async function () {
|
||||
this.server.get('/sys/internal/counters/config', function () {
|
||||
return CONFIG_RESPONSE;
|
||||
});
|
||||
sinon.replace(timestamp, 'now', sinon.fake.returns(STATIC_NOW));
|
||||
syncHandler(this.server);
|
||||
server.get('/sys/activation-flags', () => {
|
||||
return {
|
||||
data: {
|
||||
activated: [''],
|
||||
unactivated: ['secrets-sync'],
|
||||
},
|
||||
};
|
||||
});
|
||||
await authPage.login();
|
||||
return visit('/vault/clients/counts/sync');
|
||||
});
|
||||
|
||||
test('it should show an empty state when secrets sync is not activated', async function (assert) {
|
||||
assert.expect(3);
|
||||
assert.expect(2);
|
||||
|
||||
this.server.get('/sys/activation-flags', () => {
|
||||
assert.true(true, '/sys/activation-flags/ is called to check if secrets-sync is activated');
|
||||
|
||||
// called once from the higher level cluster route
|
||||
return {
|
||||
data: {
|
||||
activated: [],
|
||||
|
||||
@@ -14,7 +14,7 @@ import { settled, click, visit, currentURL, fillIn, currentRouteName } from '@em
|
||||
import { PAGE as ts } from 'vault/tests/helpers/sync/sync-selectors';
|
||||
|
||||
// sync is an enterprise feature but since mirage is used the enterprise label has been intentionally omitted from the module name
|
||||
module('Acceptance | sync | destination', function (hooks) {
|
||||
module('Acceptance | sync | destination (singular)', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
setupMirage(hooks);
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import { syncDestinations } from 'vault/helpers/sync-destinations';
|
||||
const SYNC_DESTINATIONS = syncDestinations();
|
||||
|
||||
// sync is an enterprise feature but since mirage is used the enterprise label has been intentionally omitted from the module name
|
||||
module('Acceptance | sync | destinations', function (hooks) {
|
||||
module('Acceptance | sync | destinations (plural)', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
setupMirage(hooks);
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import { setupApplicationTest } from 'ember-qunit';
|
||||
import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||
import syncScenario from 'vault/mirage/scenarios/sync';
|
||||
import syncHandlers from 'vault/mirage/handlers/sync';
|
||||
import sinon from 'sinon';
|
||||
import authPage from 'vault/tests/pages/auth';
|
||||
import { click, waitFor, visit, currentURL } from '@ember/test-helpers';
|
||||
import { PAGE as ts } from 'vault/tests/helpers/sync/sync-selectors';
|
||||
@@ -19,198 +20,288 @@ module('Acceptance | sync | overview', function (hooks) {
|
||||
setupMirage(hooks);
|
||||
|
||||
hooks.beforeEach(async function () {
|
||||
syncHandlers(this.server);
|
||||
this.version = this.owner.lookup('service:version');
|
||||
this.version.features = ['Secrets Sync'];
|
||||
|
||||
await authPage.login();
|
||||
this.permissions = this.owner.lookup('service:permissions');
|
||||
});
|
||||
|
||||
module('when feature is activated', function (hooks) {
|
||||
module('ent', function (hooks) {
|
||||
hooks.beforeEach(async function () {
|
||||
syncScenario(this.server);
|
||||
this.version.type = 'enterprise';
|
||||
});
|
||||
|
||||
test('it fetches destinations and associations', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
this.server.get('/sys/sync/destinations', () => {
|
||||
assert.true(true, 'destinations is called');
|
||||
});
|
||||
this.server.get('/sys/sync/associations', () => {
|
||||
assert.true(true, 'associations is called');
|
||||
});
|
||||
|
||||
await visit('/vault/sync/secrets/overview');
|
||||
});
|
||||
|
||||
module('when there are pre-existing destinations', function (hooks) {
|
||||
module('sync on license', function (hooks) {
|
||||
hooks.beforeEach(async function () {
|
||||
syncScenario(this.server);
|
||||
this.version.features = ['Secrets Sync'];
|
||||
});
|
||||
|
||||
test('it should transition to correct routes when performing actions', async function (assert) {
|
||||
await click(ts.navLink('Secrets Sync'));
|
||||
await click(ts.destinations.list.create);
|
||||
await click(ts.createCancel);
|
||||
await click(ts.overviewCard.actionLink('Create new'));
|
||||
await click(ts.createCancel);
|
||||
await waitFor(ts.overview.table.actionToggle(0));
|
||||
await click(ts.overview.table.actionToggle(0));
|
||||
await click(ts.overview.table.action('sync'));
|
||||
await click(ts.destinations.sync.cancel);
|
||||
await click(ts.breadcrumbLink('Secrets Sync'));
|
||||
await waitFor(ts.overview.table.actionToggle(0));
|
||||
await click(ts.overview.table.actionToggle(0));
|
||||
await click(ts.overview.table.action('details'));
|
||||
assert.dom(ts.tab('Secrets')).hasClass('active', 'Navigates to secrets view for destination');
|
||||
module('when feature is activated', function (hooks) {
|
||||
hooks.beforeEach(async function () {
|
||||
syncHandlers(this.server);
|
||||
await authPage.login();
|
||||
});
|
||||
|
||||
test('it fetches destinations and associations', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
this.server.get('/sys/sync/destinations', () => {
|
||||
assert.true(true, 'destinations is called');
|
||||
});
|
||||
this.server.get('/sys/sync/associations', () => {
|
||||
assert.true(true, 'associations is called');
|
||||
});
|
||||
|
||||
await visit('/vault/sync/secrets/overview');
|
||||
});
|
||||
|
||||
module('when there are pre-existing destinations', function (hooks) {
|
||||
hooks.beforeEach(async function () {
|
||||
syncScenario(this.server);
|
||||
await authPage.login();
|
||||
});
|
||||
|
||||
test('it should transition to correct routes when performing actions', async function (assert) {
|
||||
await click(ts.navLink('Secrets Sync'));
|
||||
await click(ts.destinations.list.create);
|
||||
await click(ts.createCancel);
|
||||
await click(ts.overviewCard.actionLink('Create new'));
|
||||
await click(ts.createCancel);
|
||||
await waitFor(ts.overview.table.actionToggle(0));
|
||||
await click(ts.overview.table.actionToggle(0));
|
||||
await click(ts.overview.table.action('sync'));
|
||||
await click(ts.destinations.sync.cancel);
|
||||
await click(ts.breadcrumbLink('Secrets Sync'));
|
||||
await waitFor(ts.overview.table.actionToggle(0));
|
||||
await click(ts.overview.table.actionToggle(0));
|
||||
await click(ts.overview.table.action('details'));
|
||||
assert.dom(ts.tab('Secrets')).hasClass('active', 'Navigates to secrets view for destination');
|
||||
});
|
||||
});
|
||||
|
||||
module('permissions', function () {
|
||||
test('users without permissions - denies access to sync page', async function (assert) {
|
||||
const hasNavPermission = sinon.stub(this.permissions, 'hasNavPermission');
|
||||
hasNavPermission.returns(false);
|
||||
|
||||
await visit('/vault/sync/secrets/overview');
|
||||
|
||||
assert.strictEqual(currentURL(), '/vault/dashboard', 'redirects to cluster dashboard route');
|
||||
});
|
||||
|
||||
test('users with permissions - allows access to sync page', async function (assert) {
|
||||
const hasNavPermission = sinon.stub(this.permissions, 'hasNavPermission');
|
||||
hasNavPermission.returns(true);
|
||||
|
||||
await visit('/vault/sync/secrets/overview');
|
||||
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
'/vault/sync/secrets/overview',
|
||||
'stays on the sync overview route'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
module('when feature is not activated', function (hooks) {
|
||||
hooks.beforeEach(async function () {
|
||||
let wasActivatePOSTCalled = false;
|
||||
// simulate the feature being activated once /secrets-sync/activate has been called
|
||||
this.server.get('/sys/activation-flags', () => {
|
||||
if (wasActivatePOSTCalled) {
|
||||
return {
|
||||
data: {
|
||||
activated: ['secrets-sync'],
|
||||
unactivated: [''],
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
data: {
|
||||
activated: [''],
|
||||
unactivated: ['secrets-sync'],
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
this.server.post('/sys/activation-flags/secrets-sync/activate', () => {
|
||||
wasActivatePOSTCalled = true;
|
||||
return {};
|
||||
});
|
||||
await authPage.login();
|
||||
});
|
||||
|
||||
test('it does not fetch destinations and associations', async function (assert) {
|
||||
assert.expect(0);
|
||||
|
||||
this.server.get('/sys/sync/destinations', () => {
|
||||
assert.true(false, 'destinations is not called');
|
||||
});
|
||||
this.server.get('/sys/sync/associations', () => {
|
||||
assert.true(false, 'associations is not called');
|
||||
});
|
||||
|
||||
await visit('/vault/sync/secrets/overview');
|
||||
});
|
||||
|
||||
test('the activation workflow works', async function (assert) {
|
||||
await visit('/vault/sync/secrets/overview');
|
||||
|
||||
assert
|
||||
.dom(ts.cta.button)
|
||||
.doesNotExist('create first destination is not available until feature has been activated');
|
||||
|
||||
assert.dom(ts.overview.optInBanner.container).exists();
|
||||
await click(ts.overview.optInBanner.enable);
|
||||
|
||||
assert
|
||||
.dom(ts.overview.activationModal.container)
|
||||
.exists('modal to opt-in and activate feature is shown');
|
||||
await click(ts.overview.activationModal.checkbox);
|
||||
await click(ts.overview.activationModal.confirm);
|
||||
|
||||
assert
|
||||
.dom(ts.overview.activationModal.container)
|
||||
.doesNotExist('modal is gone once activation has been submitted');
|
||||
assert
|
||||
.dom(ts.overview.optInBanner.container)
|
||||
.doesNotExist('opt-in banner is gone once activation has been submitted');
|
||||
|
||||
await click(ts.cta.button);
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
'/vault/sync/secrets/destinations/create',
|
||||
'create new destination is available once feature is activated'
|
||||
);
|
||||
});
|
||||
|
||||
module('enterprise with namespaces', function (hooks) {
|
||||
hooks.beforeEach(async function () {
|
||||
this.version.features = ['Secrets Sync', 'Namespaces'];
|
||||
|
||||
await runCmd(`write sys/namespaces/admin -f`, false);
|
||||
await authPage.loginNs('admin');
|
||||
await runCmd(`write sys/namespaces/foo -f`, false);
|
||||
await authPage.loginNs('admin/foo');
|
||||
});
|
||||
|
||||
test('it should make activation-flag requests to correct namespace', async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
this.server.get('/sys/activation-flags', (_, req) => {
|
||||
assert.deepEqual(req.requestHeaders, {}, 'Request is unauthenticated and in root namespace');
|
||||
return {
|
||||
data: {
|
||||
activated: [''],
|
||||
unactivated: ['secrets-sync'],
|
||||
},
|
||||
};
|
||||
});
|
||||
this.server.post('/sys/activation-flags/secrets-sync/activate', (_, req) => {
|
||||
assert.strictEqual(
|
||||
req.requestHeaders['X-Vault-Namespace'],
|
||||
undefined,
|
||||
'Request is made to undefined namespace'
|
||||
);
|
||||
return {};
|
||||
});
|
||||
|
||||
// confirm we're in admin/foo
|
||||
assert.dom('[data-test-badge-namespace]').hasText('foo');
|
||||
await click(ts.navLink('Secrets Sync'));
|
||||
await click(ts.overview.optInBanner.enable);
|
||||
await click(ts.overview.activationModal.checkbox);
|
||||
await click(ts.overview.activationModal.confirm);
|
||||
});
|
||||
|
||||
test('it should make activation-flag requests to correct namespace when managed', async function (assert) {
|
||||
assert.expect(3);
|
||||
this.owner.lookup('service:flags').featureFlags = ['VAULT_CLOUD_ADMIN_NAMESPACE'];
|
||||
|
||||
this.server.get('/sys/activation-flags', (_, req) => {
|
||||
assert.deepEqual(req.requestHeaders, {}, 'Request is unauthenticated and in root namespace');
|
||||
return {
|
||||
data: {
|
||||
activated: [''],
|
||||
unactivated: ['secrets-sync'],
|
||||
},
|
||||
};
|
||||
});
|
||||
this.server.post('/sys/activation-flags/secrets-sync/activate', (_, req) => {
|
||||
assert.strictEqual(
|
||||
req.requestHeaders['X-Vault-Namespace'],
|
||||
'admin',
|
||||
'Request is made to the admin namespace'
|
||||
);
|
||||
return {};
|
||||
});
|
||||
|
||||
// confirm we're in admin/foo
|
||||
assert.dom('[data-test-badge-namespace]').hasText('foo');
|
||||
|
||||
await click(ts.navLink('Secrets Sync'));
|
||||
await click(ts.overview.optInBanner.enable);
|
||||
await click(ts.overview.activationModal.checkbox);
|
||||
await click(ts.overview.activationModal.confirm);
|
||||
});
|
||||
});
|
||||
|
||||
module('permissions', function () {
|
||||
test('users without permissions - allows access to sync page', async function (assert) {
|
||||
const hasNavPermission = sinon.stub(this.permissions, 'hasNavPermission');
|
||||
hasNavPermission.returns(false);
|
||||
|
||||
await visit('/vault/sync/secrets/overview');
|
||||
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
'/vault/sync/secrets/overview',
|
||||
'stays on the sync overview route'
|
||||
);
|
||||
});
|
||||
|
||||
test('users with permissions - allows access to sync page', async function (assert) {
|
||||
const hasNavPermission = sinon.stub(this.permissions, 'hasNavPermission');
|
||||
hasNavPermission.returns(true);
|
||||
|
||||
await visit('/vault/sync/secrets/overview');
|
||||
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
'/vault/sync/secrets/overview',
|
||||
'stays on the sync overview route'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
module('sync NOT on license', function (hooks) {
|
||||
hooks.beforeEach(async function () {
|
||||
await authPage.login();
|
||||
|
||||
// reset features *after* login, since the login process will set the initial value according to the actual license
|
||||
this.version.features = [];
|
||||
});
|
||||
|
||||
test('it should not allow access to sync page', async function (assert) {
|
||||
await visit('/vault/sync/secrets/overview');
|
||||
|
||||
assert.strictEqual(currentURL(), '/vault/dashboard', 'redirects to cluster dashboard route');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
module('when feature is not activated', function (hooks) {
|
||||
module('oss', function (hooks) {
|
||||
hooks.beforeEach(async function () {
|
||||
let wasActivatePOSTCalled = false;
|
||||
|
||||
// simulate the feature being activated once /secrets-sync/activate has been called
|
||||
this.server.get('/sys/activation-flags', () => {
|
||||
if (wasActivatePOSTCalled) {
|
||||
return {
|
||||
data: {
|
||||
activated: ['secrets-sync'],
|
||||
unactivated: [''],
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
data: {
|
||||
activated: [''],
|
||||
unactivated: ['secrets-sync'],
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
this.server.post('/sys/activation-flags/secrets-sync/activate', () => {
|
||||
wasActivatePOSTCalled = true;
|
||||
return {};
|
||||
});
|
||||
this.version.type = 'community';
|
||||
await authPage.login();
|
||||
});
|
||||
|
||||
test('it does not fetch destinations and associations', async function (assert) {
|
||||
assert.expect(0);
|
||||
|
||||
this.server.get('/sys/sync/destinations', () => {
|
||||
assert.true(false, 'destinations is not called');
|
||||
});
|
||||
this.server.get('/sys/sync/associations', () => {
|
||||
assert.true(false, 'associations is not called');
|
||||
});
|
||||
|
||||
await visit('/vault/sync/secrets/overview');
|
||||
});
|
||||
|
||||
test('the activation workflow works', async function (assert) {
|
||||
test('it should not allow access to sync page', async function (assert) {
|
||||
await visit('/vault/sync/secrets/overview');
|
||||
|
||||
assert
|
||||
.dom(ts.cta.button)
|
||||
.doesNotExist('create first destination is not available until feature has been activated');
|
||||
|
||||
assert.dom(ts.overview.optInBanner.container).exists();
|
||||
await click(ts.overview.optInBanner.enable);
|
||||
|
||||
assert
|
||||
.dom(ts.overview.activationModal.container)
|
||||
.exists('modal to opt-in and activate feature is shown');
|
||||
await click(ts.overview.activationModal.checkbox);
|
||||
await click(ts.overview.activationModal.confirm);
|
||||
|
||||
assert
|
||||
.dom(ts.overview.activationModal.container)
|
||||
.doesNotExist('modal is gone once activation has been submitted');
|
||||
assert
|
||||
.dom(ts.overview.optInBanner.container)
|
||||
.doesNotExist('opt-in banner is gone once activation has been submitted');
|
||||
|
||||
await click(ts.cta.button);
|
||||
assert.strictEqual(
|
||||
currentURL(),
|
||||
'/vault/sync/secrets/destinations/create',
|
||||
'create new destination is available once feature is activated'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
module('enterprise with namespaces', function (hooks) {
|
||||
hooks.beforeEach(async function () {
|
||||
this.version.features = ['Secrets Sync', 'Namespaces'];
|
||||
await runCmd(`write sys/namespaces/admin -f`, false);
|
||||
await authPage.loginNs('admin');
|
||||
await runCmd(`write sys/namespaces/foo -f`, false);
|
||||
await authPage.loginNs('admin/foo');
|
||||
});
|
||||
|
||||
test('it should make activation-flag requests to correct namespace', async function (assert) {
|
||||
assert.expect(4);
|
||||
// should call GET activation-flags twice because we need an updated response after activating the feature
|
||||
this.server.get('/sys/activation-flags', (_, req) => {
|
||||
assert.deepEqual(req.requestHeaders, {}, 'Request is unauthenticated and in root namespace');
|
||||
return {
|
||||
data: {
|
||||
activated: [''],
|
||||
unactivated: ['secrets-sync'],
|
||||
},
|
||||
};
|
||||
});
|
||||
this.server.post('/sys/activation-flags/secrets-sync/activate', (_, req) => {
|
||||
assert.strictEqual(
|
||||
req.requestHeaders['X-Vault-Namespace'],
|
||||
undefined,
|
||||
'Request is made to undefined namespace'
|
||||
);
|
||||
return {};
|
||||
});
|
||||
|
||||
// confirm we're in admin/foo
|
||||
assert.dom('[data-test-badge-namespace]').hasText('foo');
|
||||
|
||||
await click(ts.navLink('Secrets Sync'));
|
||||
await click(ts.overview.optInBanner.enable);
|
||||
await click(ts.overview.activationModal.checkbox);
|
||||
await click(ts.overview.activationModal.confirm);
|
||||
});
|
||||
|
||||
test('it should make activation-flag requests to correct namespace when managed', async function (assert) {
|
||||
assert.expect(4);
|
||||
// should call GET activation-flags twice because we need an updated response after activating the feature
|
||||
this.owner.lookup('service:flags').featureFlags = ['VAULT_CLOUD_ADMIN_NAMESPACE'];
|
||||
|
||||
this.server.get('/sys/activation-flags', (_, req) => {
|
||||
assert.deepEqual(req.requestHeaders, {}, 'Request is unauthenticated and in root namespace');
|
||||
return {
|
||||
data: {
|
||||
activated: [''],
|
||||
unactivated: ['secrets-sync'],
|
||||
},
|
||||
};
|
||||
});
|
||||
this.server.post('/sys/activation-flags/secrets-sync/activate', (_, req) => {
|
||||
assert.strictEqual(
|
||||
req.requestHeaders['X-Vault-Namespace'],
|
||||
'admin',
|
||||
'Request is made to the admin namespace'
|
||||
);
|
||||
return {};
|
||||
});
|
||||
|
||||
// confirm we're in admin/foo
|
||||
assert.dom('[data-test-badge-namespace]').hasText('foo');
|
||||
|
||||
await click(ts.navLink('Secrets Sync'));
|
||||
await click(ts.overview.optInBanner.enable);
|
||||
await click(ts.overview.activationModal.checkbox);
|
||||
await click(ts.overview.activationModal.confirm);
|
||||
assert.strictEqual(currentURL(), '/vault/dashboard', 'redirects to cluster dashboard route');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,10 +13,12 @@ module('Integration | Component | clients/counts/nav-bar', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.showSecretsSync = false;
|
||||
this.showSecretsSyncClientCounts = false;
|
||||
|
||||
this.renderComponent = async () => {
|
||||
await render(hbs`<Clients::Counts::NavBar @showSecretsSync={{this.showSecretsSync}} />`);
|
||||
await render(
|
||||
hbs`<Clients::Counts::NavBar @showSecretsSyncClientCounts={{this.showSecretsSyncClientCounts}} />`
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
@@ -28,15 +30,15 @@ module('Integration | Component | clients/counts/nav-bar', function (hooks) {
|
||||
assert.dom(GENERAL.tab('acme')).hasText('ACME clients');
|
||||
});
|
||||
|
||||
test('it shows secrets sync tab if showSecretsSync is true', async function (assert) {
|
||||
this.showSecretsSync = true;
|
||||
test('it shows secrets sync tab if showSecretsSyncClientCounts is true', async function (assert) {
|
||||
this.showSecretsSyncClientCounts = true;
|
||||
await this.renderComponent();
|
||||
|
||||
assert.dom(GENERAL.tab('sync')).exists();
|
||||
});
|
||||
|
||||
test('it should not show secrets sync tab if showSecretsSync is false', async function (assert) {
|
||||
this.showSecretsSync = false;
|
||||
test('it should not show secrets sync tab if showSecretsSyncClientCounts is false', async function (assert) {
|
||||
this.showSecretsSyncClientCounts = false;
|
||||
await this.renderComponent();
|
||||
|
||||
assert.dom(GENERAL.tab('sync')).doesNotExist();
|
||||
|
||||
@@ -52,7 +52,7 @@ module('Integration | Component | sidebar-nav-cluster', function (hooks) {
|
||||
|
||||
assert
|
||||
.dom('[data-test-sidebar-nav-link]')
|
||||
.exists({ count: 3 }, 'Nav links are hidden other than secrets, secrets sync and dashboard');
|
||||
.exists({ count: 2 }, 'Nav links are hidden other than secrets and dashboard');
|
||||
assert
|
||||
.dom('[data-test-sidebar-nav-heading]')
|
||||
.exists({ count: 1 }, 'Headings are hidden other than Vault');
|
||||
@@ -84,31 +84,6 @@ module('Integration | Component | sidebar-nav-cluster', function (hooks) {
|
||||
});
|
||||
});
|
||||
|
||||
test('it should render badge for promotional links on community version', async function (assert) {
|
||||
const promotionalLinks = ['Secrets Sync'];
|
||||
// if no features passed, it defaults to all features and we need to specifically remove Secrets Sync
|
||||
stubFeaturesAndPermissions(this.owner, false, true, []);
|
||||
await renderComponent();
|
||||
|
||||
promotionalLinks.forEach((link) => {
|
||||
assert
|
||||
.dom(`[data-test-sidebar-nav-link="${link}"]`)
|
||||
.hasText(`${link} Enterprise`, `${link} link renders Enterprise badge`);
|
||||
});
|
||||
});
|
||||
|
||||
test('it should render badge for promotional links on enterprise version', async function (assert) {
|
||||
const promotionalLinks = ['Secrets Sync'];
|
||||
stubFeaturesAndPermissions(this.owner, true, true, ['Namespaces']);
|
||||
await renderComponent();
|
||||
|
||||
promotionalLinks.forEach((link) => {
|
||||
assert
|
||||
.dom(`[data-test-sidebar-nav-link="${link}"]`)
|
||||
.hasText(`${link} Premium`, `${link} link renders Premium badge`);
|
||||
});
|
||||
});
|
||||
|
||||
test('it should hide enterprise related links in child namespace', async function (assert) {
|
||||
const links = [
|
||||
'Disaster Recovery',
|
||||
|
||||
@@ -61,42 +61,12 @@ module('Integration | Component | sync | Page::Overview', function (hooks) {
|
||||
assert.dom(overview.createDestination).hasText('Create new destination', 'Toolbar action renders');
|
||||
});
|
||||
|
||||
module('community', function (hooks) {
|
||||
hooks.beforeEach(function () {
|
||||
this.version.type = 'community';
|
||||
this.isActivated = false;
|
||||
this.licenseHasSecretsSync = false;
|
||||
this.destinations = [];
|
||||
});
|
||||
|
||||
test('it should show an upsell CTA', async function (assert) {
|
||||
await this.renderComponent();
|
||||
|
||||
assert
|
||||
.dom(title)
|
||||
.hasText('Secrets Sync Enterprise feature', 'page title indicates feature is only for Enterprise');
|
||||
assert.dom(cta.button).doesNotExist();
|
||||
assert.dom(cta.summary).exists();
|
||||
});
|
||||
});
|
||||
|
||||
module('ent', function (hooks) {
|
||||
hooks.beforeEach(function () {
|
||||
this.isActivated = false;
|
||||
this.destinations = [];
|
||||
});
|
||||
|
||||
test('it should show an upsell CTA if license does NOT have the secrets sync feature', async function (assert) {
|
||||
this.version.features = [];
|
||||
await this.renderComponent();
|
||||
|
||||
assert
|
||||
.dom(title)
|
||||
.hasText('Secrets Sync Premium feature', 'title indicates feature is only for Premium');
|
||||
assert.dom(cta.button).doesNotExist();
|
||||
assert.dom(cta.summary).exists();
|
||||
});
|
||||
|
||||
test('it should show create CTA if license has the secrets sync feature', async function (assert) {
|
||||
this.version.features = ['Secrets Sync'];
|
||||
this.isActivated = true;
|
||||
@@ -182,7 +152,7 @@ module('Integration | Component | sync | Page::Overview', function (hooks) {
|
||||
});
|
||||
});
|
||||
|
||||
module('secrets sync is not activated and license has secrets sync', function (hooks) {
|
||||
module('secrets sync is not activated', function (hooks) {
|
||||
hooks.beforeEach(async function () {
|
||||
this.isActivated = false;
|
||||
});
|
||||
@@ -263,18 +233,6 @@ module('Integration | Component | sync | Page::Overview', function (hooks) {
|
||||
});
|
||||
});
|
||||
|
||||
module('secrets sync is not activated and license does not have secrets sync', function (hooks) {
|
||||
hooks.beforeEach(async function () {
|
||||
this.licenseHasSecretsSync = false;
|
||||
});
|
||||
|
||||
test('it should hide the opt-in banner', async function (assert) {
|
||||
await this.renderComponent();
|
||||
|
||||
assert.dom(overview.optInBanner.container).doesNotExist();
|
||||
});
|
||||
});
|
||||
|
||||
module('secrets sync is activated', function () {
|
||||
test('it should hide the opt-in banner', async function (assert) {
|
||||
await this.renderComponent();
|
||||
|
||||
@@ -44,25 +44,6 @@ module('Integration | Component | sync | SyncHeader', function (hooks) {
|
||||
|
||||
assert.dom(title).hasText('Secrets Sync');
|
||||
});
|
||||
|
||||
test('it should render title and premium badge if license does not have secrets sync feature', async function (assert) {
|
||||
this.version.features = [];
|
||||
await this.renderComponent();
|
||||
|
||||
assert.dom(title).hasText('Secrets Sync Premium feature');
|
||||
});
|
||||
});
|
||||
|
||||
module('community', function (hooks) {
|
||||
hooks.beforeEach(function () {
|
||||
this.version.type = 'community';
|
||||
});
|
||||
|
||||
test('it should render title and enterprise badge', async function (assert) {
|
||||
await this.renderComponent();
|
||||
|
||||
assert.dom(title).hasText('Secrets Sync Enterprise feature');
|
||||
});
|
||||
});
|
||||
|
||||
module('managed', function (hooks) {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||
import sinon from 'sinon';
|
||||
|
||||
const ACTIVATED_FLAGS_RESPONSE = {
|
||||
data: {
|
||||
@@ -24,6 +25,8 @@ module('Unit | Service | flags', function (hooks) {
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
this.service = this.owner.lookup('service:flags');
|
||||
this.version = this.owner.lookup('service:version');
|
||||
this.permissions = this.owner.lookup('service:permissions');
|
||||
});
|
||||
|
||||
test('it loads with defaults', function (assert) {
|
||||
@@ -33,7 +36,7 @@ module('Unit | Service | flags', function (hooks) {
|
||||
|
||||
module('#fetchActivatedFlags', function (hooks) {
|
||||
hooks.beforeEach(function () {
|
||||
this.owner.lookup('service:version').type = 'enterprise';
|
||||
this.version.type = 'enterprise';
|
||||
});
|
||||
|
||||
test('it returns activated flags', async function (assert) {
|
||||
@@ -66,8 +69,16 @@ module('Unit | Service | flags', function (hooks) {
|
||||
assert.deepEqual(this.service.activatedFlags, [], 'Activated flags are empty');
|
||||
});
|
||||
|
||||
test('it returns an empty array if the cluster is OSS', async function (assert) {
|
||||
this.owner.lookup('service:version').type = 'community';
|
||||
test('it does not call activation-flags endpoint if the cluster is OSS', async function (assert) {
|
||||
this.version.type = 'community';
|
||||
|
||||
this.server.get(
|
||||
'sys/activation-flags',
|
||||
() =>
|
||||
new Error(
|
||||
'uh oh! a request was made to sys/activation-flags, this should not happen for community versions'
|
||||
)
|
||||
);
|
||||
|
||||
await this.service.fetchActivatedFlags();
|
||||
assert.deepEqual(this.service.activatedFlags, [], 'Activated flags are empty');
|
||||
@@ -76,7 +87,7 @@ module('Unit | Service | flags', function (hooks) {
|
||||
|
||||
module('#fetchFeatureFlags', function (hooks) {
|
||||
hooks.beforeEach(function () {
|
||||
this.owner.lookup('service:version').type = 'enterprise';
|
||||
this.version.type = 'enterprise';
|
||||
});
|
||||
|
||||
test('it returns feature flags', async function (assert) {
|
||||
@@ -119,9 +130,9 @@ module('Unit | Service | flags', function (hooks) {
|
||||
});
|
||||
});
|
||||
|
||||
module('#secretsSyncActivated', function (hooks) {
|
||||
module('#secretsSyncIsActivated', function (hooks) {
|
||||
hooks.beforeEach(function () {
|
||||
this.owner.lookup('service:version').type = 'enterprise';
|
||||
this.version.type = 'enterprise';
|
||||
this.service.activatedFlags = ACTIVATED_FLAGS_RESPONSE.data.activated;
|
||||
});
|
||||
|
||||
@@ -134,4 +145,78 @@ module('Unit | Service | flags', function (hooks) {
|
||||
assert.false(this.service.secretsSyncIsActivated);
|
||||
});
|
||||
});
|
||||
|
||||
module('#showSecretsSync', function () {
|
||||
test('it returns false when version is community', function (assert) {
|
||||
this.version.type = 'community';
|
||||
assert.false(this.service.showSecretsSync);
|
||||
});
|
||||
|
||||
module('isHvdManaged', function (hooks) {
|
||||
hooks.beforeEach(function () {
|
||||
this.version.type = 'enterprise';
|
||||
this.service.featureFlags = ['VAULT_CLOUD_ADMIN_NAMESPACE'];
|
||||
});
|
||||
|
||||
test('it returns true when not activated', function (assert) {
|
||||
this.service.activatedFlags = [];
|
||||
assert.true(this.service.showSecretsSync);
|
||||
});
|
||||
|
||||
test('it returns true when activated', function (assert) {
|
||||
this.service.activatedFlags = ACTIVATED_FLAGS_RESPONSE.data.activated;
|
||||
assert.true(this.service.showSecretsSync);
|
||||
});
|
||||
});
|
||||
|
||||
module('is Enterprise', function (hooks) {
|
||||
hooks.beforeEach(function () {
|
||||
this.version.type = 'enterprise';
|
||||
});
|
||||
|
||||
test('it returns false when not on license ', function (assert) {
|
||||
this.version.features = ['replication'];
|
||||
assert.false(this.service.showSecretsSync);
|
||||
});
|
||||
|
||||
module('no permissions to sys/sync', function (hooks) {
|
||||
hooks.beforeEach(function () {
|
||||
this.version.features = ['Secrets Sync'];
|
||||
const hasNavPermission = sinon.stub(this.permissions, 'hasNavPermission');
|
||||
hasNavPermission.returns(false);
|
||||
});
|
||||
|
||||
test('it returns false when activated ', function (assert) {
|
||||
this.service.activatedFlags = ACTIVATED_FLAGS_RESPONSE.data.activated;
|
||||
assert.false(this.service.showSecretsSync);
|
||||
});
|
||||
|
||||
test('it returns true when not activated ', function (assert) {
|
||||
// the activate endpoint is located at a different path than sys/sync.
|
||||
// the expected UX experience: if the feature is not activated, regardless of permissions
|
||||
// the user should see the landing page and a banner that tells them to either have an admin activate the feature or activate it themselves
|
||||
this.service.activatedFlags = [];
|
||||
assert.true(this.service.showSecretsSync);
|
||||
});
|
||||
});
|
||||
|
||||
module('user has permissions to sys/sync', function (hooks) {
|
||||
hooks.beforeEach(function () {
|
||||
this.version.features = ['Secrets Sync'];
|
||||
const hasNavPermission = sinon.stub(this.permissions, 'hasNavPermission');
|
||||
hasNavPermission.returns(true);
|
||||
});
|
||||
|
||||
test('it returns true when activated ', function (assert) {
|
||||
this.service.activatedFlags = ACTIVATED_FLAGS_RESPONSE.data.activated;
|
||||
assert.true(this.service.showSecretsSync);
|
||||
});
|
||||
|
||||
test('it returns true when not activated ', function (assert) {
|
||||
this.service.activatedFlags = [];
|
||||
assert.true(this.service.showSecretsSync);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
1
ui/types/vault/services/permissions.d.ts
vendored
1
ui/types/vault/services/permissions.d.ts
vendored
@@ -16,4 +16,5 @@ export default class PermissionsService extends Service {
|
||||
canViewAll: boolean | null;
|
||||
permissionsBanner: string | null;
|
||||
chrootNamespace: string | null | undefined;
|
||||
hasNavPermission: (string) => boolean;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user