Refactor edition enablement (#1657)

* refactor: 💡 consolidate ssh features into ssh-target flag

✅ Closes: ICU-8383

* refactor: 💡 factor out base edition

* feat: 🎸 dynamic feature edition enablement and toggling
This commit is contained in:
Randall Morey authored and GitHub committed 2023-03-17 14:34:37 -07:00
1 parent bd6eee8eb3
commit acd719140d
24 files changed
+303 -91

No files matched your search

@@ -45,3 +45,5 @@ learn-more: Learn more
refresh: Refresh refresh: Refresh
remove-resources: Remove resources and save remove-resources: Remove resources and save
do-this-later: Do this later do-this-later: Do this later
toggle-color-theme: Toggle Color Theme
toggle-feature-edition: Toggle Feature Edition
+6 -1
View File
@@ -28,7 +28,6 @@ titles:
actions: Actions actions: Actions
documentation: Documentation documentation: Documentation
abandon-confirm: Discard unsaved changes? abandon-confirm: Discard unsaved changes?
toggle-color-theme: Toggle Color Theme
welcome-to-boundary: Welcome to Boundary welcome-to-boundary: Welcome to Boundary
label: Label label: Label
value: Value value: Value
@@ -38,6 +37,12 @@ titles:
success: Success success: Success
create-resources: Create Resources create-resources: Create Resources
choose-an-option: Choose an option choose-an-option: Choose an option
feature-edition: 'Edition: {edition}'
editions:
oss: OSS
ent: Enterprise
enterprise: Enterprise
hcp: HCP
descriptions: descriptions:
empty-set: There are no items to display yet. You may be able to add items or try back later. empty-set: There are no items to display yet. You may be able to add items or try back later.
cluster-url-initialization: To get started, please enter your cluster URL cluster-url-initialization: To get started, please enter your cluster URL
+3 -4
View File
@@ -12,13 +12,12 @@ export default class OverrideCredentialLibraryAbility extends CredentialLibraryA
/** /**
* This override ensures that vault ssh cert may be read only if the * This override ensures that vault ssh cert may be read only if the
* credential-library-vault-ssh-certificate feature flag is enabled. * ssh-target feature flag is enabled.
* All other types are subject to the standard logic found in the api addon. * All other types are subject to the standard logic found in the api addon.
*/ */
get canRead() { get canRead() {
return this.features.isEnabled( return this.features.isEnabled('ssh-target') ||
'credential-library-vault-ssh-certificate' !this.model.isVaultSSHCertificate
) || !this.model.isVaultSSHCertificate
? super.canRead ? super.canRead
: false; : false;
} }
@@ -50,9 +50,7 @@
{{/if}} {{/if}}
</Hds::Form::Textarea::Field> </Hds::Form::Textarea::Field>
{{#if {{#if (and @model.isNew (feature-flag 'ssh-target'))}}
(and @model.isNew (feature-flag 'credential-library-vault-ssh-certificate'))
}}
<Hds::Form::RadioCard::Group @name='type' @alignment='center' as |G|> <Hds::Form::RadioCard::Group @name='type' @alignment='center' as |G|>
<G.Legend>{{t 'form.type.label'}}</G.Legend> <G.Legend>{{t 'form.type.label'}}</G.Legend>
{{#each this.types as |type|}} {{#each this.types as |type|}}
@@ -6,7 +6,7 @@
<div class='target-worker-filter' data-test-filter={{@name}}> <div class='target-worker-filter' data-test-filter={{@name}}>
<Hds::Form::Toggle::Field <Hds::Form::Toggle::Field
name='target-worker-filter-toggle' name={{concat 'target-worker-filter-toggle-' @name}}
checked={{@toggleEnabled}} checked={{@toggleEnabled}}
disabled={{@disabled}} disabled={{@disabled}}
@isOptional={{true}} @isOptional={{true}}
+1
View File
@@ -11,4 +11,5 @@ export default class ApplicationController extends Controller {
@service session; @service session;
@service flashMessages; @service flashMessages;
@service featureEdition;
} }
@@ -0,0 +1,8 @@
export function initialize(owner) {
const service = owner.lookup('service:feature-edition');
service.initialize();
}
export default {
initialize,
};
+6
View File
@@ -20,6 +20,7 @@ export default class ApplicationRoute extends Route {
@service confirm; @service confirm;
@service router; @service router;
@service intl; @service intl;
@service featureEdition;
// =attributes // =attributes
@@ -126,4 +127,9 @@ export default class ApplicationRoute extends Route {
rootEl.classList.remove('rose-theme-light'); rootEl.classList.remove('rose-theme-light');
} }
} }
@action
toggleEdition(edition) {
this.featureEdition.setEdition(edition);
}
} }
@@ -36,7 +36,7 @@ export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrari
// Set the type to generic vault if feature flag isn't enabled in cases where // Set the type to generic vault if feature flag isn't enabled in cases where
// user sets the query parameter manually // user sets the query parameter manually
if (!this.features.isEnabled('credential-library-vault-ssh-certificate')) { if (!this.features.isEnabled('ssh-target')) {
type = TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC; type = TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC;
} }
+42
View File
@@ -0,0 +1,42 @@
import Service from '@ember/service';
import { inject as service } from '@ember/service';
import { tracked } from '@glimmer/tracking';
import config from 'admin/config/environment';
const { selectedEdition, featureEditions } = config;
export default class FeatureEditionService extends Service {
// =services
@service features;
// =attributes
@tracked edition;
// array of edition strings
get editions() {
return Object.keys(featureEditions);
}
// =methods
/**
* Initializes the selected edition from the config.
*/
initialize() {
this.setEdition(selectedEdition);
}
setEdition(edition) {
const editionFlags = featureEditions[edition];
Object.keys(editionFlags).forEach((flag) => {
if (editionFlags[flag]) {
this.features.enable(flag);
} else {
this.features.disable(flag);
}
});
this.edition = edition;
}
}
+34 -1
View File
@@ -33,7 +33,7 @@
<dropdown.separator /> <dropdown.separator />
<dropdown.section @title={{t 'titles.toggle-color-theme'}}> <dropdown.section @title={{t 'actions.toggle-color-theme'}}>
<Rose::Form as |form|> <Rose::Form as |form|>
<form.radioGroup <form.radioGroup
@name='theme' @name='theme'
@@ -67,6 +67,39 @@
</dropdown.section> </dropdown.section>
</utilities.dropdown> </utilities.dropdown>
{{#if (feature-flag 'dev-edition-toggle')}}
<utilities.dropdown
data-test-dev-edition-toggle
@text={{t
'titles.feature-edition'
edition=(t (concat 'editions.' this.featureEdition.edition))
}}
@icon='flight-icons/svg/layers-16'
as |dropdown|
>
<dropdown.section @title={{t 'actions.toggle-feature-edition'}}>
<Rose::Form as |form|>
<form.radioGroup
@name='edition'
@selectedValue={{this.featureEdition.edition}}
@changed={{route-action 'toggleEdition'}}
as |radioGroup|
>
{{#each this.featureEdition.editions as |edition|}}
<dropdown.item>
<radioGroup.radio
@label={{t (concat 'editions.' edition)}}
@value={{edition}}
/>
</dropdown.item>
{{/each}}
</form.radioGroup>
</Rose::Form>
</dropdown.section>
</utilities.dropdown>
{{/if}}
</header.utilities> </header.utilities>
{{/if}} {{/if}}
+54 -36
View File
@@ -9,28 +9,36 @@ const APP_NAME = process.env.APP_NAME || 'Boundary';
const API_HOST = process.env.API_HOST || ''; const API_HOST = process.env.API_HOST || '';
const EDITION = process.env.EDITION || 'oss'; // Default edition is OSS const EDITION = process.env.EDITION || 'oss'; // Default edition is OSS
// Object that defines edition features. // Base edition declares available features, disabled by default.
const featureEditions = { const baseEdition = {
oss: { byow: false,
'static-credentials': true, 'byow-pki-hcp-cluster-id': false,
'json-credentials': true, 'byow-pki-upstream': false,
byow: true, 'json-credentials': false,
'byow-pki-hcp-cluster-id': false, 'ssh-target': false,
'byow-pki-upstream': true, 'static-credentials': false,
'vault-worker-filter': false, 'target-worker-filters-v2': false,
'target-worker-filters-v2': true, 'target-worker-filters-v2-ingress': false,
'target-worker-filters-v2-ingress': false, 'target-worker-filters-v2-hcp': false,
'target-worker-filters-v2-hcp': false, 'target-network-address': false,
'target-network-address': true, 'vault-worker-filter': false,
'credential-library-vault-ssh-certificate': false, };
}, // Editions maps edition keys to their associated featuresets.
const featureEditions = {};
featureEditions.oss = {
...baseEdition,
byow: true,
'byow-pki-upstream': true,
'json-credentials': true,
'static-credentials': true,
'target-worker-filters-v2': true,
'target-network-address': true,
}; };
featureEditions.enterprise = { featureEditions.enterprise = {
...featureEditions.oss, ...featureEditions.oss,
'ssh-target': true, 'ssh-target': true,
'vault-worker-filter': true,
'target-worker-filters-v2-ingress': true, 'target-worker-filters-v2-ingress': true,
'credential-library-vault-ssh-certificate': true, 'vault-worker-filter': true,
}; };
featureEditions.hcp = { featureEditions.hcp = {
...featureEditions.enterprise, ...featureEditions.enterprise,
@@ -127,7 +135,20 @@ module.exports = function (environment) {
timeout: 4000, timeout: 4000,
}, },
featureFlags: featureEditions[EDITION], selectedEdition: EDITION,
featureEditions,
featureFlags: {},
};
/**
* Takes a set of features and enables them in all editions.
* For use in development and testing only.
*/
const enableFeaturesInAllEditions = (features = {}) => {
Object.entries(featureEditions).forEach(
([key, edition]) =>
(ENV.featureEditions[key] = { ...features, ...edition })
);
}; };
if (environment === 'development') { if (environment === 'development') {
@@ -151,16 +172,12 @@ module.exports = function (environment) {
}, },
}; };
// Enable features in development // Default edition in development
ENV.featureFlags['static-credentials'] = true; ENV.selectedEdition = 'enterprise';
ENV.featureFlags['json-credentials'] = true; enableFeaturesInAllEditions({
ENV.featureFlags['ssh-target'] = true; // Show edition toggle in UI in development
ENV.featureFlags['vault-worker-filter'] = true; 'dev-edition-toggle': true,
ENV.featureFlags['target-worker-filters-v2'] = true; });
ENV.featureFlags['target-worker-filters-v2-ingress'] = true;
ENV.featureFlags['target-worker-filters-v2-hcp'] = true;
ENV.featureFlags['target-network-address'] = true;
ENV.featureFlags['credential-library-vault-ssh-certificate'] = true;
} }
if (environment === 'test') { if (environment === 'test') {
@@ -179,14 +196,15 @@ module.exports = function (environment) {
ENV.enableConfirmService = false; ENV.enableConfirmService = false;
// Enable tests for development features /**
ENV.featureFlags['json-credentials'] = true; * Enable additional features for testing purposes.
ENV.featureFlags['static-credentials'] = true; * [TODO] Tests should be refactored such that each test case
ENV.featureFlags['ssh-target'] = true; * explicitly enables the features and/or edition that it needs.
ENV.featureFlags['vault-worker-filter'] = true; * By default, tests should have no features enabled. This explicit
ENV.featureFlags['target-worker-filters-v2'] = true; * approach ensures that test cases are easy to read and understand,
ENV.featureFlags['target-network-address'] = true; * since their feature requirements are embedded.
ENV.featureFlags['credential-library-vault-ssh-certificate'] = true; */
ENV.selectedEdition = 'enterprise';
} }
if (environment === 'production') { if (environment === 'production') {
@@ -225,7 +225,7 @@ module('Acceptance | credential-libraries | create', function (hooks) {
test('cannot select vault ssh cert when feature is disabled', async function (assert) { test('cannot select vault ssh cert when feature is disabled', async function (assert) {
const featuresService = this.owner.lookup('service:features'); const featuresService = this.owner.lookup('service:features');
featuresService.disable('credential-library-vault-ssh-certificate'); featuresService.disable('ssh-target');
assert.expect(1); assert.expect(1);
await visit(urls.newCredentialLibrary); await visit(urls.newCredentialLibrary);
@@ -95,7 +95,7 @@ module('Acceptance | credential-libraries | read', function (hooks) {
`${urls.credentialLibraries}/${instances.credentialLibrary.id}` `${urls.credentialLibraries}/${instances.credentialLibrary.id}`
); );
const featuresService = this.owner.lookup('service:features'); const featuresService = this.owner.lookup('service:features');
featuresService.disable('credential-library-vault-ssh-certificate'); featuresService.disable('ssh-target');
await visit(urls.credentialLibraries); await visit(urls.credentialLibraries);
assert.dom('.rose-table-body tr:nth-of-type(2) a').doesNotExist(); assert.dom('.rose-table-body tr:nth-of-type(2) a').doesNotExist();
+29
View File
@@ -0,0 +1,29 @@
import { module, test } from 'qunit';
import { visit, getContext } from '@ember/test-helpers';
import { setupApplicationTest } from 'admin/tests/helpers';
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
module('Acceptance | develop', function (hooks) {
setupApplicationTest(hooks);
setupMirage(hooks);
let featuresService;
hooks.beforeEach(function () {
const { owner } = getContext();
featuresService = owner.lookup('service:features');
});
test('edition toggle is hidden when `dev-edition-toggle` is false', async function (assert) {
await visit('/');
assert.false(featuresService.isEnabled('dev-edition-toggle'));
assert.dom('data-dev-edition-toggle').doesNotExist();
});
test('edition toggle is visible when `dev-edition-toggle` is true', async function (assert) {
assert.false(featuresService.isEnabled('dev-edition-toggle'));
featuresService.enable('dev-edition-toggle');
await visit('/');
assert.dom('data-dev-edition-toggle').doesNotExist();
});
});
@@ -125,7 +125,10 @@ module('Acceptance | targets | create', function (hooks) {
await click(`[href="${urls.newTarget}"]`); await click(`[href="${urls.newTarget}"]`);
await click('[value="ssh"]'); await click('[value="ssh"]');
await fillIn('[name="name"]', 'random string'); await fillIn('[name="name"]', 'random string');
await click('.hds-form-toggle__control', 'Egress worker filter'); await click(
'[name="target-worker-filter-toggle-egress_worker_filter"]',
'Egress toggle'
);
await fillIn('[name="egress_worker_filter"]', 'random filter'); await fillIn('[name="egress_worker_filter"]', 'random filter');
await click('[type="submit"]'); await click('[type="submit"]');
@@ -178,7 +181,10 @@ module('Acceptance | targets | create', function (hooks) {
await click(`[href="${urls.newTarget}"]`); await click(`[href="${urls.newTarget}"]`);
await click('[value="tcp"]'); await click('[value="tcp"]');
await fillIn('[name="name"]', 'random string'); await fillIn('[name="name"]', 'random string');
await click('.hds-form-toggle__control', 'Egress worker filter'); await click(
'[name="target-worker-filter-toggle-egress_worker_filter"]',
'Egress toggle'
);
await fillIn('[name="egress_worker_filter"]', 'random filter'); await fillIn('[name="egress_worker_filter"]', 'random filter');
await click('[type="submit"]'); await click('[type="submit"]');
@@ -301,7 +307,10 @@ module('Acceptance | targets | create', function (hooks) {
await click(`[href="${urls.newTarget}"]`); await click(`[href="${urls.newTarget}"]`);
await fillIn('[name="name"]', 'random string'); await fillIn('[name="name"]', 'random string');
await click('.hds-form-toggle__control', 'Egress worker filter'); await click(
'[name="target-worker-filter-toggle-egress_worker_filter"]',
'Egress toggle'
);
await fillIn('[name="egress_worker_filter"]', 'random filter'); await fillIn('[name="egress_worker_filter"]', 'random filter');
await click('.rose-form-actions [type="button"]'); await click('.rose-form-actions [type="button"]');
@@ -318,7 +327,10 @@ module('Acceptance | targets | create', function (hooks) {
await click(`[href="${urls.newTarget}"]`); await click(`[href="${urls.newTarget}"]`);
await fillIn('[name="name"]', 'random string'); await fillIn('[name="name"]', 'random string');
await click('[value="ssh"]'); await click('[value="ssh"]');
await click('.hds-form-toggle__control', 'Egress worker filter'); await click(
'[name="target-worker-filter-toggle-egress_worker_filter"]',
'Egress toggle'
);
await fillIn('[name="egress_worker_filter"]', 'random filter'); await fillIn('[name="egress_worker_filter"]', 'random filter');
await click('.rose-form-actions [type="button"]'); await click('.rose-form-actions [type="button"]');
@@ -164,16 +164,21 @@ module('Acceptance | targets | update', function (hooks) {
test('hide filter input field when the `egress_worker_filter` toggled is off', async function (assert) { test('hide filter input field when the `egress_worker_filter` toggled is off', async function (assert) {
featuresService.enable('target-worker-filters-v2'); featuresService.enable('target-worker-filters-v2');
featuresService.enable('target-worker-filters-v2-ingress');
assert.expect(1); assert.expect(1);
await visit(urls.target); await visit(urls.target);
await click('.rose-form-actions [type="button"]', 'Activate edit mode'); await click('.rose-form-actions [type="button"]', 'Activate edit mode');
await click('.hds-button[type="button"]', 'Update Filter'); await click('.hds-button[type="button"]', 'Update Filter');
await click('.hds-form-toggle__control', 'Egress worker filter'); await click(
'[name="target-worker-filter-toggle-egress_worker_filter"]',
'Egress toggle'
);
assert.dom('[name=egress_worker_filter]').isNotVisible(); assert.dom('[name=egress_worker_filter]').isNotVisible();
}); });
test('clear `egress_worker_field` value from a target when the toggle is off and form is saved', async function (assert) { test('clear `egress_worker_field` value from a target when the toggle is off and form is saved', async function (assert) {
featuresService.enable('target-worker-filters-v2'); featuresService.enable('target-worker-filters-v2');
featuresService.enable('target-worker-filters-v2-ingress');
assert.expect(5); assert.expect(5);
await visit(urls.target); await visit(urls.target);
await click('.rose-form-actions [type="button"]', 'Activate edit mode'); await click('.rose-form-actions [type="button"]', 'Activate edit mode');
@@ -186,7 +191,10 @@ module('Acceptance | targets | update', function (hooks) {
'random filter string' 'random filter string'
); );
await click('.rose-form-actions [type="button"]', 'Activate edit mode'); await click('.rose-form-actions [type="button"]', 'Activate edit mode');
await click('.hds-form-toggle__control', 'Egress worker filter'); await click(
'[name="target-worker-filter-toggle-egress_worker_filter"]',
'Egress toggle'
);
await click('.rose-form-actions [type="submit"]'); await click('.rose-form-actions [type="submit"]');
//clear egress_worker_filter when the toggle is off //clear egress_worker_filter when the toggle is off
assert.strictEqual(currentURL(), urls.target); assert.strictEqual(currentURL(), urls.target);
@@ -137,9 +137,9 @@ test('Static Credential Store (User & Key Pair)', async ({ page }) => {
if (process.env.E2E_SSH_USER != retrievedUser) { if (process.env.E2E_SSH_USER != retrievedUser) {
throw new Error( throw new Error(
'Stored User does not match. EXPECTED: ' + 'Stored User does not match. EXPECTED: ' +
process.env.E2E_SSH_USER + process.env.E2E_SSH_USER +
', ACTUAL: ' + ', ACTUAL: ' +
retrievedUser retrievedUser
); );
} }
if (keyData != retrievedKey) { if (keyData != retrievedKey) {
@@ -59,8 +59,8 @@ test('Vault Credential Store (User & Key Pair)', async ({ page }) => {
execSync(`vault secrets enable -path=${secretsPath} kv-v2`); execSync(`vault secrets enable -path=${secretsPath} kv-v2`);
execSync( execSync(
`vault kv put -mount ${secretsPath} ${secretName} ` + `vault kv put -mount ${secretsPath} ${secretName} ` +
` username=${process.env.E2E_SSH_USER}` + ` username=${process.env.E2E_SSH_USER}` +
` private_key=@${process.env.E2E_SSH_KEY_PATH}` ` private_key=@${process.env.E2E_SSH_KEY_PATH}`
); );
execSync( execSync(
`vault policy write ${secretPolicyName} ./tests/e2e/tests/fixtures/kv-policy.hcl` `vault policy write ${secretPolicyName} ./tests/e2e/tests/fixtures/kv-policy.hcl`
@@ -68,13 +68,13 @@ test('Vault Credential Store (User & Key Pair)', async ({ page }) => {
const vaultToken = JSON.parse( const vaultToken = JSON.parse(
execSync( execSync(
`vault token create` + `vault token create` +
` -no-default-policy=true` + ` -no-default-policy=true` +
` -policy=${boundaryPolicyName}` + ` -policy=${boundaryPolicyName}` +
` -policy=${secretPolicyName}` + ` -policy=${secretPolicyName}` +
` -orphan=true` + ` -orphan=true` +
` -period=20m` + ` -period=20m` +
` -renewable=true` + ` -renewable=true` +
` -format=json` ` -format=json`
) )
); );
const clientToken = vaultToken.auth.client_token; const clientToken = vaultToken.auth.client_token;
@@ -175,9 +175,9 @@ test('Vault Credential Store (User & Key Pair)', async ({ page }) => {
if (process.env.E2E_SSH_USER != retrievedUser) { if (process.env.E2E_SSH_USER != retrievedUser) {
throw new Error( throw new Error(
'Stored User does not match. EXPECTED: ' + 'Stored User does not match. EXPECTED: ' +
process.env.E2E_SSH_USER + process.env.E2E_SSH_USER +
', ACTUAL: ' + ', ACTUAL: ' +
retrievedUser retrievedUser
); );
} }
if (keyData != retrievedKey) { if (keyData != retrievedKey) {
@@ -128,9 +128,9 @@ test.describe('AWS', async () => {
if (!hostsAreVisible) { if (!hostsAreVisible) {
throw new Error( throw new Error(
'Hosts are not visible. EXPECTED: ' + 'Hosts are not visible. EXPECTED: ' +
expectedHosts.length + expectedHosts.length +
', ACTUAL: ' + ', ACTUAL: ' +
rowCount rowCount
); );
} }
@@ -216,9 +216,9 @@ test.describe('AWS', async () => {
if (!hostsAreVisible) { if (!hostsAreVisible) {
throw new Error( throw new Error(
'Hosts are not visible. EXPECTED: ' + 'Hosts are not visible. EXPECTED: ' +
expectedHosts.length + expectedHosts.length +
', ACTUAL: ' + ', ACTUAL: ' +
rowCount rowCount
); );
} }
}); });
+12 -12
View File
@@ -61,18 +61,18 @@ test('Verify session created then cancel the session', async ({ page }) => {
connect = exec( connect = exec(
'boundary connect' + 'boundary connect' +
' -target-id=' + ' -target-id=' +
target.id + target.id +
' -exec /usr/bin/ssh --' + ' -exec /usr/bin/ssh --' +
' -l ' + ' -l ' +
process.env.E2E_SSH_USER + process.env.E2E_SSH_USER +
' -i ' + ' -i ' +
process.env.E2E_SSH_KEY_PATH + process.env.E2E_SSH_KEY_PATH +
' -o UserKnownHostsFile=/dev/null' + ' -o UserKnownHostsFile=/dev/null' +
' -o StrictHostKeyChecking=no' + ' -o StrictHostKeyChecking=no' +
' -o IdentitiesOnly=yes' + // forces the use of the provided key ' -o IdentitiesOnly=yes' + // forces the use of the provided key
' -p {{boundary.port}}' + ' -p {{boundary.port}}' +
' {{boundary.ip}}' ' {{boundary.ip}}'
); );
await page await page
@@ -17,7 +17,7 @@ module('Unit | Abilities | credential-library', function (hooks) {
hooks.beforeEach(function () { hooks.beforeEach(function () {
features = this.owner.lookup('service:features'); features = this.owner.lookup('service:features');
features.enable('credential-library-vault-ssh-certificate'); features.enable('ssh-target');
}); });
test('can read credential library type when authorized and feature is enabled', function (assert) { test('can read credential library type when authorized and feature is enabled', function (assert) {
@@ -51,7 +51,7 @@ module('Unit | Abilities | credential-library', function (hooks) {
const canService = this.owner.lookup('service:can'); const canService = this.owner.lookup('service:can');
const store = this.owner.lookup('service:store'); const store = this.owner.lookup('service:store');
const featuresService = this.owner.lookup('service:features'); const featuresService = this.owner.lookup('service:features');
featuresService.disable('credential-library-vault-ssh-certificate'); featuresService.disable('ssh-target');
const credentialLibrary = store.createRecord('credential-library', { const credentialLibrary = store.createRecord('credential-library', {
authorized_actions: [], authorized_actions: [],
type: TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE, type: TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE,
@@ -66,7 +66,7 @@ module('Unit | Abilities | credential-library', function (hooks) {
const canService = this.owner.lookup('service:can'); const canService = this.owner.lookup('service:can');
const store = this.owner.lookup('service:store'); const store = this.owner.lookup('service:store');
const featuresService = this.owner.lookup('service:features'); const featuresService = this.owner.lookup('service:features');
featuresService.disable('credential-library-vault-ssh-certificate'); featuresService.disable('ssh-target');
const credentialLibrary = store.createRecord('credential-library', { const credentialLibrary = store.createRecord('credential-library', {
authorized_actions: ['read'], authorized_actions: ['read'],
type: TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE, type: TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE,
@@ -0,0 +1,39 @@
import Application from '@ember/application';
import config from 'admin/config/environment';
import { initialize } from 'admin/instance-initializers/feature-edition';
import { module, test } from 'qunit';
import Resolver from 'ember-resolver';
import { run } from '@ember/runloop';
module('Unit | Instance Initializer | feature-edition', function (hooks) {
hooks.beforeEach(function () {
this.TestApplication = class TestApplication extends Application {
modulePrefix = config.modulePrefix;
podModulePrefix = config.podModulePrefix;
Resolver = Resolver;
};
this.TestApplication.instanceInitializer({
name: 'initializer under test',
initialize,
});
this.application = this.TestApplication.create({
autoboot: false,
});
this.instance = this.application.buildInstance();
});
hooks.afterEach(function () {
run(this.instance, 'destroy');
run(this.application, 'destroy');
});
// TODO: Replace this with your real tests.
test('it works', async function (assert) {
await this.instance.boot();
assert.ok(true);
});
});
@@ -0,0 +1,12 @@
import { module, test } from 'qunit';
import { setupTest } from 'admin/tests/helpers';
module('Unit | Service | features', function (hooks) {
setupTest(hooks);
// TODO: Replace this with your real tests.
test('it exists', function (assert) {
let service = this.owner.lookup('service:features');
assert.ok(service);
});
});