Merge branch 'main' into refactor-delete-jquery

This commit is contained in:
Carlos Alcaide Corvo authored and GitHub committed 2021-07-27 14:55:02 -07:00
commit 43eca819bf
23 files changed
+795 -14

No files matched your search

+1 -1
View File
@@ -29,7 +29,7 @@
"ember-cli-mirage": "^1.1.8",
"ember-data": "^3.20.0",
"ember-data-model-fragments": "5.0.0-beta.2",
"ember-fetch": "^8.0.1"
"ember-fetch": "^8.1.0"
},
"devDependencies": {
"@ember/optional-features": "^2.0.0",
+4 -8
View File
@@ -1,9 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.4577 1.67511L5.56323 7.36908L10.5292 13.3821L13.2972 11.0961L17.1724 15.5238L13.7226 18.5433L15.2044 20.2364L18.6543 17.2169L20.1097 18.8797L16.662 21.8973L18.1438 23.5904L23.2846 19.091L15.0328 9.66276L17.4237 7.68814L12.4577 1.67511Z" fill="black"/>
<mask id="path-2-inside-1" fill="white">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.766 4.35302L5.62911 4.255L5.53249 11.2901L9.12206 11.3394L9.20978 17.2227L4.62573 17.2911L4.6593 19.5409L9.24333 19.4725L9.27627 21.682L4.69506 21.7503L4.7286 24L11.5596 23.8982L11.3728 11.3703L12.6694 11.3882L12.766 4.35302Z"/>
</mask>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.766 4.35302L5.62911 4.255L5.53249 11.2901L9.12206 11.3394L9.20978 17.2227L4.62573 17.2911L4.6593 19.5409L9.24333 19.4725L9.27627 21.682L4.69506 21.7503L4.7286 24L11.5596 23.8982L11.3728 11.3703L12.6694 11.3882L12.766 4.35302Z" fill="black"/>
<path d="M5.62911 4.255L5.64971 2.75515L4.14985 2.73455L4.12925 4.23441L5.62911 4.255ZM12.766 4.35302L14.2659 4.37362L14.2865 2.87376L12.7866 2.85316L12.766 4.35302ZM5.53249 11.2901L4.03264 11.2695L4.01204 12.7694L5.5119 12.79L5.53249 11.2901ZM9.12206 11.3394L10.6219 11.3171L10.6002 9.85959L9.14265 9.83958L9.12206 11.3394ZM9.20978 17.2227L9.23216 18.7226L10.732 18.7002L10.7096 17.2004L9.20978 17.2227ZM4.62573 17.2911L4.60335 15.7913L3.10352 15.8137L3.1259 17.3135L4.62573 17.2911ZM4.6593 19.5409L3.15947 19.5633L3.18185 21.0631L4.68168 21.0407L4.6593 19.5409ZM9.24333 19.4725L10.7432 19.4501L10.7208 17.9503L9.22095 17.9727L9.24333 19.4725ZM9.27627 21.682L9.29863 23.1818L10.7985 23.1594L10.7761 21.6596L9.27627 21.682ZM4.69506 21.7503L4.6727 20.2504L3.17286 20.2728L3.19522 21.7726L4.69506 21.7503ZM4.7286 24L3.22877 24.0224L3.25113 25.5222L4.75096 25.4998L4.7286 24ZM11.5596 23.8982L11.5819 25.398L13.0818 25.3756L13.0594 23.8758L11.5596 23.8982ZM11.3728 11.3703L11.3934 9.87048L9.84992 9.84929L9.87293 11.3927L11.3728 11.3703ZM12.6694 11.3882L12.6488 12.888L14.1487 12.9086L14.1693 11.4087L12.6694 11.3882ZM5.60851 5.75486L12.7454 5.85288L12.7866 2.85316L5.64971 2.75515L5.60851 5.75486ZM7.03235 11.3107L7.12897 4.2756L4.12925 4.23441L4.03264 11.2695L7.03235 11.3107ZM9.14265 9.83958L5.55309 9.79028L5.5119 12.79L9.10146 12.8393L9.14265 9.83958ZM7.62222 11.3618L7.70995 17.2451L10.7096 17.2004L10.6219 11.3171L7.62222 11.3618ZM9.1874 15.7229L4.60335 15.7913L4.64811 18.791L9.23216 18.7226L9.1874 15.7229ZM3.1259 17.3135L3.15947 19.5633L6.15913 19.5185L6.12557 17.2688L3.1259 17.3135ZM4.68168 21.0407L9.2657 20.9723L9.22095 17.9727L4.63692 18.0411L4.68168 21.0407ZM7.74349 19.4949L7.77644 21.7043L10.7761 21.6596L10.7432 19.4501L7.74349 19.4949ZM9.25391 20.1821L4.6727 20.2504L4.71742 23.2501L9.29863 23.1818L9.25391 20.1821ZM3.19522 21.7726L3.22877 24.0224L6.22843 23.9777L6.19489 21.7279L3.19522 21.7726ZM4.75096 25.4998L11.5819 25.398L11.5372 22.3983L4.70624 22.5002L4.75096 25.4998ZM13.0594 23.8758L12.8726 11.348L9.87293 11.3927L10.0597 23.9205L13.0594 23.8758ZM12.69 9.88829L11.3934 9.87048L11.3522 12.8702L12.6488 12.888L12.69 9.88829ZM11.2662 4.33242L11.1696 11.3676L14.1693 11.4087L14.2659 4.37362L11.2662 4.33242Z" fill="black" mask="url(#path-2-inside-1)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.2796 1.26121C9.20838 0.46864 7.88301 0 6.44818 0C2.88695 0 0 2.88695 0 6.44818C0 9.00798 1.4916 11.2194 3.65306 12.2607L3.68943 9.61275C2.80735 8.84314 2.25 7.71075 2.25 6.44818C2.25 4.12959 4.12959 2.25 6.44818 2.25C7.18811 2.25 7.88333 2.44143 8.48703 2.77746L10.2796 1.26121Z" fill="black"/>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.18544 1.11673L4.58911 4.91271L7.89977 8.92139L9.74509 7.3974L12.3286 10.3492L10.0287 12.3622L11.0166 13.4909L13.3165 11.4779L14.2867 12.5865L11.9883 14.5982L12.9762 15.7269L16.4034 12.7273L10.9021 6.44183L12.4961 5.12541L9.18544 1.11673Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.39097 2.9997L4.63303 2.93436L4.56862 7.62444L6.96166 7.65731L7.02014 11.5795L3.96411 11.6251L3.98649 13.1249L7.04251 13.0793L7.06447 14.5523L4.01033 14.5979L4.03269 16.0977L8.58667 16.0298L8.46213 7.67792L9.32656 7.68979L9.39097 2.9997Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.73342 0.840799C7.01928 0.312425 6.13571 0 5.17915 0C2.805 0 0.880371 1.92463 0.880371 4.29878C0.880371 6.00532 1.87477 7.47961 3.31575 8.1738L3.33999 6.4085C2.75194 5.89543 2.38037 5.1405 2.38037 4.29878C2.38037 2.75306 3.63343 1.5 5.17915 1.5C5.67244 1.5 6.13592 1.62762 6.53838 1.85163L7.73342 0.840799Z" fill="currentColor"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -254,6 +254,7 @@ target:
create: New Target
delete: Delete Target
add-host-sets: Add Host Sets
add-credential-libraries: Add Credential Libraries
types:
tcp: TCP
host-set:
@@ -267,6 +268,17 @@ target:
add:
title: Add Host Sets
description: Select host sets to assign to this target.
credential-library:
messages:
welcome:
title: Welcome to Credential Libraries
description: No credential libraries in this target.
none:
title: No Credential Libraries Available
description: No credential libraries available to add to this target.
add:
title: Add Credential Libraries
description: Select credential libraries to assign to this target.
credential-store:
title: Credential Store
title_plural: Credential Stores
@@ -282,6 +294,7 @@ credential-library:
description: A credential library is a resource that provides credentials.
actions:
create: New Credential Library
delete: Delete Credential Library
messages:
none:
title: No Credential Libraries Available
@@ -0,0 +1,55 @@
{{#if this.hasAvailableCredentialLibraries}}
<Rose::Form
class='full-width'
@onSubmit={{fn this.submit @submit}}
@cancel={{@cancel}}
@disabled={{@model.isSaving}}
@error={{@model.errors.base}}
as |form|
>
<form.actions
@submitText={{t 'resources.target.actions.add-credential-libraries'}}
@cancelText={{t 'actions.cancel'}}
/>
<Rose::Table as |table|>
<table.header as |header|>
<header.row as |row|>
<row.headerCell>{{t 'form.id.label'}}</row.headerCell>
<row.headerCell>{{t 'form.name.label'}}</row.headerCell>
</header.row>
</table.header>
<table.body as |body|>
{{#each this.filteredCredentialLibraries as |credentialLibrary|}}
<body.row as |row|>
<row.headerCell>
<form.checkbox
@label={{credentialLibrary.id}}
@description={{credentialLibrary.description}}
onChange={{fn this.toggleCredentialLibrary credentialLibrary}}
/>
</row.headerCell>
<row.cell>{{credentialLibrary.name}}</row.cell>
</body.row>
{{/each}}
</table.body>
</Rose::Table>
</Rose::Form>
{{/if}}
{{#unless this.hasAvailableCredentialLibraries}}
<Rose::Layout::Centered>
<Rose::Message
@title={{t 'resources.target.credential-library.messages.none.title'}}
as |message|
>
<message.description>
{{t 'resources.target.credential-library.messages.none.description'}}
</message.description>
<message.link @route='scopes.scope.targets.target.credential-libraries'>
<Rose::Icon @name='chevron-left' />
{{t 'actions.back'}}
</message.link>
</Rose::Message>
</Rose::Layout::Centered>
{{/unless}}
@@ -0,0 +1,65 @@
import Component from '@glimmer/component';
import { computed, action } from '@ember/object';
import { A } from '@ember/array';
export default class FormTargetAddCredentialLibrariesIndexComponent extends Component {
// =properties
/**
* Array of selected credential library IDs.
* @type {EmberArray}
*/
selectedCredentialLibraryIDs = A();
/**
* Checks for unassigned credential libraries.
* @param {[CredentialLibraryModel]} filteredCredentialLibraries
* @type {boolean}
*/
@computed('filteredCredentialLibraries.length')
get hasAvailableCredentialLibraries() {
return this.filteredCredentialLibraries.length > 0;
}
/**
* Filter out credential libraries not already added to the target.
* @type {[CredentialLibraryModel]}
*/
@computed(
'args.{credentialLibraries.[],model.application_credential_library_ids.[]}'
)
get filteredCredentialLibraries() {
// Get IDs for credential libraries already added to the current target
const currentCredentialLibraryIDs =
this.args.model.application_credential_library_ids.map(
(credentialLibrary) => credentialLibrary.value
);
const notAddedCredentialLibraries = this.args.credentialLibraries.filter(
({ id }) => !currentCredentialLibraryIDs.includes(id)
);
return notAddedCredentialLibraries;
}
// =actions
/**
* Add/Remove credential library to current selection
* @param CredentialLibraryModel
*/
@action
toggleCredentialLibrary(credentialLibrary) {
if (!this.selectedCredentialLibraryIDs.includes(credentialLibrary.id)) {
this.selectedCredentialLibraryIDs.addObject(credentialLibrary.id);
} else {
this.selectedCredentialLibraryIDs.removeObject(credentialLibrary.id);
}
}
/**
* Submit selected credential library ids
*/
@action
submit(fn) {
fn(this.selectedCredentialLibraryIDs);
}
}
@@ -0,0 +1,18 @@
import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
export default class ScopesScopeTargetsTargetCredentialLibrariesController extends Controller {
// =services
@service intl;
// =attributes
/**
* Translated credential library breadcrumb
* @type {string}
*/
get breadCrumb() {
return this.intl.t('resources.credential-library.title_plural');
}
}
+2
View File
@@ -65,6 +65,8 @@ Router.map(function () {
this.route('target', { path: ':target_id' }, function () {
this.route('host-sets', function () {});
this.route('add-host-sets');
this.route('credential-libraries');
this.route('add-credential-libraries');
});
this.route('new');
});
@@ -1,6 +1,7 @@
import Route from '@ember/routing/route';
import { action } from '@ember/object';
import loading from 'ember-loading/decorator';
import { confirm } from 'core/decorators/confirm';
import { notifySuccess, notifyError } from 'core/decorators/notify';
export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrariesRoute extends Route {
@@ -50,4 +51,21 @@ export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrari
);
this.refresh();
}
/**
* Handle delete of a credential library
* @param {CredentialLibraryModel} credentialLibrary
*/
@action
@loading
@confirm('questions.delete-confirm')
@notifyError(({ message }) => message, { catch: true })
@notifySuccess('notifications.delete-success')
async delete(credentialLibrary) {
await credentialLibrary.destroyRecord();
await this.replaceWith(
'scopes.scope.credential-stores.credential-store.credential-libraries'
);
this.refresh();
}
}
@@ -0,0 +1,94 @@
import Route from '@ember/routing/route';
import { inject as service } from '@ember/service';
import { all } from 'rsvp';
import { action } from '@ember/object';
import loading from 'ember-loading/decorator';
import { notifySuccess, notifyError } from 'core/decorators/notify';
export default class ScopesScopeTargetsTargetAddCredentialLibrariesRoute extends Route {
// =services
@service intl;
@service notify;
// =methods
/**
* Empty out any previously loaded credential libraries.
*/
beforeModel() {
this.store.unloadAll('credential-library');
}
/**
* Returns the current target and all credential libraries.
* @return {{target: TargetModel, credentialLibraries: [CredentialLibraryModel]}}
*/
async model() {
const target = this.modelFor('scopes.scope.targets.target');
const { id: scope_id } = this.modelFor('scopes.scope');
const credentialStores = await this.store.query('credential-store', {
scope_id,
});
await all(
credentialStores.map(({ id: credential_store_id }) =>
this.store.query('credential-library', { credential_store_id })
)
);
const credentialLibraries = this.store.peekAll('credential-library');
return {
target,
credentialLibraries,
};
}
/**
* Renders credential libraries specific header template.
* @override
*/
renderTemplate() {
super.renderTemplate(...arguments);
this.render(
'scopes/scope/targets/target/add-credential-libraries/-header',
{
into: 'scopes/scope/targets/target',
outlet: 'header',
}
);
this.render('-empty', {
into: 'scopes/scope/targets/target',
outlet: 'navigation',
});
this.render('-empty', {
into: 'scopes/scope/targets/target',
outlet: 'actions',
});
}
// =actions
/**
* Add credential libraries to current target
* @param {TargetModel} target
* @param {[string]} credentialLibraryIDs
*/
@action
@loading
@notifyError(({ message }) => message, { catch: true })
@notifySuccess('notifications.add-success')
async save(target, credentialLibraryIDs) {
await target.addCredentialLibraries(credentialLibraryIDs);
this.replaceWith('scopes.scope.targets.target.credential-libraries');
}
/**
* Redirect to target credential libraries as if nothing ever happened.
*/
@action
cancel() {
this.replaceWith('scopes.scope.targets.target.credential-libraries');
}
}
@@ -0,0 +1,51 @@
import Route from '@ember/routing/route';
import { all } from 'rsvp';
import { action } from '@ember/object';
import loading from 'ember-loading/decorator';
import { confirm } from 'core/decorators/confirm';
import { notifySuccess, notifyError } from 'core/decorators/notify';
export default class ScopesScopeTargetsTargetCredentialLibrariesRoute extends Route {
// =methods
/**
* Loads all credential libraries under the current target.
* @return {Promise{[CredentialLibraryModel]}}
*/
beforeModel() {
const { application_credential_library_ids: credential_libraries } =
this.modelFor('scopes.scope.targets.target');
return all(
credential_libraries.map((credential_library) =>
this.store.findRecord('credential-library', credential_library.value, {
reload: true,
})
)
);
}
/**
* Returns the previously loaded target instance.
* @return {TargetModel}
*/
model() {
return this.modelFor('scopes.scope.targets.target');
}
// =actions
/**
* Remove a credential library from the current target.
* @param {TargetModel} target
* @param {CredentialLibraryModel} credentialLibrary
*/
@action
@loading
@confirm('questions.remove-confirm')
@notifyError(({ message }) => message, { catch: true })
@notifySuccess('notifications.remove-success')
async removeCredentialLibrary(target, credentialLibrary) {
await target.removeCredentialLibrary(credentialLibrary.id);
this.refresh();
}
}
+1 -1
View File
@@ -85,7 +85,7 @@
</nav.link>
{{#if (feature-flag 'credential-store')}}
<nav.link @route='scopes.scope.credential-stores'>
<Rose::Icon @name='app-icons/credential' @size='medium' />
<Rose::Icon @name='app-icons/credentials' @size='medium' />
{{t 'resources.credential-store.title_plural'}}
</nav.link>
{{/if}}
@@ -0,0 +1,13 @@
<Rose::Dropdown
@text={{t 'actions.manage'}}
@dropdownRight={{true}}
as |dropdown|
>
<dropdown.button
@style='danger'
@disabled={{@model.canSave}}
{{on 'click' (route-action 'delete' @model)}}
>
{{t 'resources.credential-library.actions.delete'}}
</dropdown.button>
</Rose::Dropdown>
@@ -6,6 +6,13 @@
<dropdown.link @route='scopes.scope.targets.target.add-host-sets'>
{{t 'resources.target.actions.add-host-sets'}}
</dropdown.link>
{{#if (feature-flag 'credential-store')}}
<dropdown.link
@route='scopes.scope.targets.target.add-credential-libraries'
>
{{t 'resources.target.actions.add-credential-libraries'}}
</dropdown.link>
{{/if}}
<dropdown.separator />
<dropdown.button
@style='danger'
@@ -5,4 +5,9 @@
<nav.link @route='scopes.scope.targets.target.host-sets'>
{{t 'resources.host-set.title_plural'}}
</nav.link>
{{#if (feature-flag 'credential-store')}}
<nav.link @route='scopes.scope.targets.target.credential-libraries'>
{{t 'resources.credential-library.title_plural'}}
</nav.link>
{{/if}}
</Rose::Nav::Tabs>
@@ -0,0 +1,27 @@
{{page-title (t 'resources.target.actions.add-credential-libraries')}}
{{#if @model.credentialLibraries}}
<Form::Target::AddCredentialLibraries
@model={{@model.target}}
@credentialLibraries={{@model.credentialLibraries}}
@submit={{route-action 'save' @model.target}}
@cancel={{route-action 'cancel'}}
/>
{{/if}}
{{#unless @model.credentialLibraries}}
<Rose::Layout::Centered>
<Rose::Message
@title={{t 'resources.target.credential-library.messages.none.title'}}
as |message|
>
<message.description>
{{t 'resources.target.credential-library.messages.none.description'}}
</message.description>
<message.link @route='scopes.scope.targets.target.credential-libraries'>
<Rose::Icon @name='chevron-left' />
{{t 'actions.back'}}
</message.link>
</Rose::Message>
</Rose::Layout::Centered>
{{/unless}}
@@ -0,0 +1,4 @@
<h2>
{{t 'resources.target.credential-library.messages.add.title'}}
</h2>
<p>{{t 'resources.target.credential-library.messages.add.description'}}</p>
@@ -0,0 +1,77 @@
{{page-title (t 'resources.credential-library.title_plural')}}
{{#if @model.credentialLibraries}}
<Rose::Table as |table|>
<table.header as |header|>
<header.row as |row|>
<row.headerCell>{{t 'form.name.label'}}</row.headerCell>
<row.headerCell>{{t 'form.id.label'}}</row.headerCell>
<row.headerCell>{{t 'titles.actions'}}</row.headerCell>
</header.row>
</table.header>
<table.body as |body|>
{{#each @model.credentialLibraries as |credentialLibrary|}}
<body.row as |row|>
<row.headerCell>
<LinkTo
@route='scopes.scope.credential-stores.credential-store.credential-libraries.credential-library'
@models={{array
credentialLibrary.credential_store_id
credentialLibrary.id
}}
>
{{credentialLibrary.displayName}}
</LinkTo>
<p>{{credentialLibrary.description}}</p>
</row.headerCell>
<row.cell>
<Copyable
@text={{credentialLibrary.id}}
@buttonText={{t 'actions.copy-to-clipboard'}}
@acknowledgeText={{t 'states.copied'}}
>
<code>{{credentialLibrary.id}}</code>
</Copyable>
</row.cell>
<row.cell>
<Rose::Dropdown
@icon='more-horizontal'
@iconOnly={{true}}
@showCaret={{false}}
@dropdownRight={{true}}
@text={{t 'actions.manage'}}
as |dropdown|
>
<dropdown.button
@style='danger'
{{on
'click'
(route-action
'removeCredentialLibrary' @model credentialLibrary
)
}}
>
{{t 'actions.remove'}}
</dropdown.button>
</Rose::Dropdown>
</row.cell>
</body.row>
{{/each}}
</table.body>
</Rose::Table>
{{else}}
<Rose::Layout::Centered>
<Rose::Message
@title={{t 'resources.target.credential-library.messages.welcome.title'}}
as |message|
>
<message.description>
{{t 'resources.target.credential-library.messages.welcome.description'}}
</message.description>
</Rose::Message>
</Rose::Layout::Centered>
{{/if}}
@@ -0,0 +1,189 @@
import { module, test } from 'qunit';
import { visit, click, fillIn, currentURL, find } from '@ember/test-helpers';
import { setupApplicationTest } from 'ember-qunit';
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';
import { Response } from 'miragejs';
import { resolve, reject } from 'rsvp';
import sinon from 'sinon';
module('Acceptance | credential-library', function (hooks) {
setupApplicationTest(hooks);
setupMirage(hooks);
let getCredentialLibraryCount;
const instances = {
scopes: {
global: null,
org: null,
project: null,
},
};
const urls = {
globalScope: null,
orgScope: null,
projectScope: null,
credentialStores: null,
credentialStore: null,
credentialLibrary: null,
credentialLibraries: null,
newCredentialLibrary: null,
unknownCredentialLibrary: null,
};
hooks.beforeEach(function () {
// Generate resources
instances.scopes.global = this.server.create('scope', { id: 'global' });
instances.scopes.org = this.server.create('scope', {
type: 'org',
scope: { id: 'global', type: 'global' },
});
instances.scopes.project = this.server.create('scope', {
type: 'project',
scope: { id: instances.scopes.org.id, type: 'org' },
});
instances.credentialStore = this.server.create('credential-store', {
scope: instances.scopes.project,
});
instances.credentialLibrary = this.server.create('credential-library', {
scope: instances.scopes.project,
});
// Generate route URLs for resources
urls.globalScope = `/scopes/global/scopes`;
urls.orgScope = `/scopes/${instances.scopes.org.id}/scopes`;
urls.projectScope = `/scopes/${instances.scopes.project.id}`;
urls.credentialStores = `${urls.projectScope}/credential-stores`;
urls.credentialStore = `${urls.credentialStores}/${instances.credentialStore.id}`;
urls.credentialLibraries = `${urls.credentialStore}/credential-libraries`;
urls.credentialLibrary = `${urls.credentialLibraries}/${instances.credentialLibrary.id}`;
urls.newCredentialLibrary = `${urls.credentialLibraries}/new`;
urls.unknownCredentialLibrary = `${urls.credentialLibraries}/foo`;
// Generate resource counter
getCredentialLibraryCount = () =>
this.server.schema.credentialLibraries.all().models.length;
authenticateSession({});
});
test('visiting credential libraries', async function (assert) {
assert.expect(2);
await visit(urls.credentialLibraries);
await a11yAudit();
assert.equal(currentURL(), urls.credentialLibraries);
await visit(urls.credentialLibrary);
await a11yAudit();
assert.equal(currentURL(), urls.credentialLibrary);
});
test('visiting an unknown credential library displays 404 message', async function (assert) {
assert.expect(1);
await visit(urls.unknownCredentialLibrary);
await a11yAudit();
console.debug(find('.rose-message-subtitle'));
assert.ok(find('.rose-message-subtitle').textContent.trim(), 'Error 404');
});
test('can create a credential library', async function (assert) {
assert.expect(1);
const count = getCredentialLibraryCount();
await visit(urls.newCredentialLibrary);
await fillIn('[name="name"]', 'random string');
await click('[type="submit"]');
assert.equal(getCredentialLibraryCount(), count + 1);
});
test('can cancel create a new credential library', async function (assert) {
assert.expect(2);
const count = getCredentialLibraryCount();
await visit(urls.newCredentialLibrary);
await fillIn('[name="name"]', 'random string');
await click('.rose-form-actions [type="button"]');
assert.equal(currentURL(), urls.credentialLibraries);
assert.equal(getCredentialLibraryCount(), count);
});
test('saving a new credential library with invalid fields displays error messasges', async function (assert) {
assert.expect(2);
this.server.post('/credential-libraries', () => {
return new Response(
400,
{},
{
status: 400,
code: 'invalid_argument',
message: 'The request was invalid',
details: {
request_fields: [
{
name: 'name',
description: 'Name is required.',
},
],
},
}
);
});
await visit(urls.newCredentialLibrary);
await click('[type="submit"]');
assert.ok(
find('[role="alert"]').textContent.trim(),
'The request was invalid.'
);
assert.ok(
find('.rose-form-error-message').textContent.trim(),
'Name is required.'
);
});
test('can delete credential library', async function (assert) {
assert.expect(1);
const count = getCredentialLibraryCount();
await visit(urls.credentialLibrary);
await click('.rose-layout-page-actions .rose-dropdown-button-danger');
assert.equal(getCredentialLibraryCount(), count - 1);
});
test('can accept delete credential library via dialog', async function (assert) {
assert.expect(2);
const confirmService = this.owner.lookup('service:confirm');
confirmService.enabled = true;
confirmService.confirm = sinon.fake.returns(resolve());
const count = getCredentialLibraryCount();
await visit(urls.credentialLibrary);
await click('.rose-layout-page-actions .rose-dropdown-button-danger');
assert.equal(getCredentialLibraryCount(), count - 1);
assert.ok(confirmService.confirm.calledOnce);
});
test('cannot cancel delete credential library via dialog', async function (assert) {
assert.expect(2);
const confirmService = this.owner.lookup('service:confirm');
confirmService.enabled = true;
confirmService.confirm = sinon.fake.returns(reject());
const count = getCredentialLibraryCount();
await visit(urls.credentialLibrary);
await click('.rose-layout-page-actions .rose-dropdown-button-danger');
assert.equal(getCredentialLibraryCount(), count);
assert.ok(confirmService.confirm.calledOnce);
});
test('deleting a credential library which errors displays error messages', async function (assert) {
assert.expect(1);
this.server.del('/credential-libraries/:id', () => {
return new Response(
490,
{},
{
status: 490,
code: 'error',
message: 'Oops.',
}
);
});
await visit(urls.credentialLibrary);
await click('.rose-layout-page-actions .rose-dropdown-button-danger');
assert.ok(find('[role="alert"]').textContent.trim(), 'Oops.');
});
});
@@ -0,0 +1,98 @@
import { module, test } from 'qunit';
import { visit, find, findAll, click } from '@ember/test-helpers';
import { setupApplicationTest } from 'ember-qunit';
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
import { Response } from 'miragejs';
import {
authenticateSession,
// These are left here intentionally for future reference.
//currentSession,
//invalidateSession,
} from 'ember-simple-auth/test-support';
module('Acceptance | targets | credential-libraries', function (hooks) {
setupApplicationTest(hooks);
setupMirage(hooks);
let getCredentialLibraryCount;
const instances = {
scopes: {
global: null,
org: null,
project: null,
},
credentialLibraries: null,
};
const urls = {
globalScope: null,
orgScope: null,
projectScope: null,
targets: null,
target: null,
targetCredentialLibraries: null,
};
hooks.beforeEach(function () {
// Generate resources
instances.scopes.global = this.server.create('scope', { id: 'global' });
instances.scopes.org = this.server.create('scope', {
type: 'org',
scope: { id: 'global', type: 'global' },
});
instances.scopes.project = this.server.create('scope', {
type: 'project',
scope: { id: instances.scopes.org.id, type: 'org' },
});
instances.credentialLibraries = this.server.createList(
'credential-library',
2,
{ scope: instances.scopes.project }
);
instances.target = this.server.create('target', {
scope: instances.scopes.project,
credentialLibraries: instances.credentialLibraries,
});
// Generate route URLs for resources
urls.globalScope = `/scopes/global/scopes`;
urls.orgScope = `/scopes/${instances.scopes.org.id}/scopes`;
urls.projectScope = `/scopes/${instances.scopes.project.id}`;
urls.targets = `/scopes/${instances.scopes.project.id}/targets`;
urls.target = `${urls.targets}/${instances.target.id}`;
urls.targetCredentialLibraries = `${urls.target}/credential-libraries`;
// Generate resource counter
getCredentialLibraryCount = () =>
this.server.schema.credentialLibraries.all().models.length;
authenticateSession({});
});
test('can remove a credential library', async function (assert) {
assert.expect(2);
const count = getCredentialLibraryCount();
await visit(urls.targetCredentialLibraries);
assert.equal(findAll('tbody tr').length, count);
await click('tbody tr .rose-dropdown-button-danger');
assert.equal(findAll('tbody tr').length, count - 1);
});
test('removing a target credential library which errors displays error messages', async function (assert) {
assert.expect(2);
this.server.post('/targets/:idMethod', () => {
return new Response(
400,
{},
{
status: 400,
code: 'invalid_argument',
message: 'The request was invalid.',
details: {},
}
);
});
const count = getCredentialLibraryCount();
await visit(urls.targetCredentialLibraries);
assert.equal(findAll('tbody tr').length, count);
await click('tbody tr .rose-dropdown-button-danger');
assert.ok(find('[role="alert"]'));
});
});
@@ -0,0 +1,17 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module(
'Unit | Controller | scopes/scope/targets/target/credential-libraries',
function (hooks) {
setupTest(hooks);
// TODO: Replace this with your real tests.
test('it exists', function (assert) {
let controller = this.owner.lookup(
'controller:scopes/scope/targets/target/credential-libraries'
);
assert.ok(controller);
});
}
);
@@ -0,0 +1,16 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module(
'Unit | Route | scopes/scope/targets/target/add-credential-libraries',
function (hooks) {
setupTest(hooks);
test('it exists', function (assert) {
let route = this.owner.lookup(
'route:scopes/scope/targets/target/add-credential-libraries'
);
assert.ok(route);
});
}
);
@@ -0,0 +1,16 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
module(
'Unit | Route | scopes/scope/targets/target/credential-libraries',
function (hooks) {
setupTest(hooks);
test('it exists', function (assert) {
let route = this.owner.lookup(
'route:scopes/scope/targets/target/credential-libraries'
);
assert.ok(route);
});
}
);
+4 -4
View File
@@ -9236,10 +9236,10 @@ ember-feature-flags@^6.0.0:
dependencies:
ember-cli-babel "^7.11.1"
ember-fetch@^8.0.1:
version "8.0.5"
resolved "https://registry.yarnpkg.com/ember-fetch/-/ember-fetch-8.0.5.tgz#0ed6257b3158473f662e72831cab223010c5ec50"
integrity sha512-tKwSWZ2l4jKynqUuF8qgwiCfzmnRWDDO2j5l0Lrn4TgZo8YXbdM3DfgheLSZd+aybBFEAtXBgKH0qaTox28LFg==
ember-fetch@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/ember-fetch/-/ember-fetch-8.1.0.tgz#6228f49baaf6f435af1e5c767e19ef638ff28883"
integrity sha512-797IPPuXrWGAecsAuCPH6+2pJCcawy7+CiRrtoe1qLhXJ6Ue0Lxu4loXxHHQNCxRvvUvx8tTDuP63sOKfi3xug==
dependencies:
abortcontroller-polyfill "^1.7.1"
broccoli-concat "^4.2.5"