From 2212c7dd9ec5819e28cda502cf824ddea9ac99c5 Mon Sep 17 00:00:00 2001 From: Susmitha Girumala Date: Sun, 18 Jul 2021 12:34:33 -0400 Subject: [PATCH 1/5] feat: List credential libraries in a credential store --- addons/core/translations/resources/en-us.yaml | 8 ++++ ui/admin/app/router.js | 4 +- .../credential-store/credential-libraries.js | 16 +++++++ .../credential-libraries/index.js | 3 ++ .../credential-store/-navigation.hbs | 5 +++ .../credential-store/credential-libraries.hbs | 3 ++ .../credential-libraries/index.hbs | 44 +++++++++++++++++++ .../credential-libraries-test.js | 16 +++++++ .../credential-libraries/index-test.js | 16 +++++++ 9 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credential-libraries.js create mode 100644 ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credential-libraries/index.js create mode 100644 ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credential-libraries.hbs create mode 100644 ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credential-libraries/index.hbs create mode 100644 ui/admin/tests/unit/routes/scopes/scope/credential-stores/credential-store/credential-libraries-test.js create mode 100644 ui/admin/tests/unit/routes/scopes/scope/credential-stores/credential-store/credential-libraries/index-test.js diff --git a/addons/core/translations/resources/en-us.yaml b/addons/core/translations/resources/en-us.yaml index 2c9712a67..cc9fed978 100644 --- a/addons/core/translations/resources/en-us.yaml +++ b/addons/core/translations/resources/en-us.yaml @@ -271,3 +271,11 @@ credential-store: title: Credential Store title_plural: Credential Stores description: A credential stores is a collection of credentials and credential libraries. +credential-library: + title: Credential Library + title_plural: Credential Libraries + description: A credential library is a resource that provides credentials. + messages: + none: + title: No Credential Libraries Available + description: No Credential Libraries available in this credential store. \ No newline at end of file diff --git a/ui/admin/app/router.js b/ui/admin/app/router.js index 665582957..4012791b2 100644 --- a/ui/admin/app/router.js +++ b/ui/admin/app/router.js @@ -90,7 +90,9 @@ Router.map(function () { this.route( 'credential-store', { path: ':credential_store_id' }, - function () {} + function () { + this.route('credential-libraries', function () {}); + } ); this.route('new'); }); diff --git a/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credential-libraries.js b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credential-libraries.js new file mode 100644 index 000000000..7c8ade755 --- /dev/null +++ b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credential-libraries.js @@ -0,0 +1,16 @@ +import Route from '@ember/routing/route'; + +export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrariesRoute extends Route { + // =methods + + /** + * Loads all credential libraries under the current credential store. + * @return {Promise{[CredentialLibraryModel]}} + */ + model() { + const { id: credential_store_id } = this.modelFor( + 'scopes.scope.credential-stores.credential-store' + ); + return this.store.query('credential-library', { credential_store_id }); + } +} diff --git a/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credential-libraries/index.js b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credential-libraries/index.js new file mode 100644 index 000000000..5b017ab54 --- /dev/null +++ b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credential-libraries/index.js @@ -0,0 +1,3 @@ +import Route from '@ember/routing/route'; + +export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrariesIndexRoute extends Route {} diff --git a/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/-navigation.hbs b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/-navigation.hbs index 86b9cf585..1fedd07d3 100644 --- a/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/-navigation.hbs +++ b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/-navigation.hbs @@ -2,4 +2,9 @@ {{t 'titles.details'}} + + {{t 'resources.credential-library.title_plural'}} + \ No newline at end of file diff --git a/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credential-libraries.hbs b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credential-libraries.hbs new file mode 100644 index 000000000..75899503d --- /dev/null +++ b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credential-libraries.hbs @@ -0,0 +1,3 @@ +{{page-title (t 'resources.credential-library.title_plural')}} + +{{outlet}} \ No newline at end of file diff --git a/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credential-libraries/index.hbs b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credential-libraries/index.hbs new file mode 100644 index 000000000..403a2175a --- /dev/null +++ b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credential-libraries/index.hbs @@ -0,0 +1,44 @@ +{{#if @model}} + + + + + {{t 'form.name.label'}} + {{t 'form.id.label'}} + + + + {{#each @model as |credentialLibrary|}} + + + {{credentialLibrary.displayName}} +

{{credentialLibrary.description}}

+
+ + + {{credentialLibrary.id}} + + +
+ {{/each}} +
+
+ +{{else}} + + + + + {{t 'resources.credential-library.messages.none.description'}} + + + + +{{/if}} \ No newline at end of file diff --git a/ui/admin/tests/unit/routes/scopes/scope/credential-stores/credential-store/credential-libraries-test.js b/ui/admin/tests/unit/routes/scopes/scope/credential-stores/credential-store/credential-libraries-test.js new file mode 100644 index 000000000..80b64d780 --- /dev/null +++ b/ui/admin/tests/unit/routes/scopes/scope/credential-stores/credential-store/credential-libraries-test.js @@ -0,0 +1,16 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module( + 'Unit | Route | scopes/scope/credential-stores/credential-store/credential-libraries', + function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let route = this.owner.lookup( + 'route:scopes/scope/credential-stores/credential-store/credential-libraries' + ); + assert.ok(route); + }); + } +); diff --git a/ui/admin/tests/unit/routes/scopes/scope/credential-stores/credential-store/credential-libraries/index-test.js b/ui/admin/tests/unit/routes/scopes/scope/credential-stores/credential-store/credential-libraries/index-test.js new file mode 100644 index 000000000..9ee1756fe --- /dev/null +++ b/ui/admin/tests/unit/routes/scopes/scope/credential-stores/credential-store/credential-libraries/index-test.js @@ -0,0 +1,16 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module( + 'Unit | Route | scopes/scope/credential-stores/credential-store/credential-libraries/index', + function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let route = this.owner.lookup( + 'route:scopes/scope/credential-stores/credential-store/credential-libraries/index' + ); + assert.ok(route); + }); + } +); From 9c93ff6c131d531c640da3e6f02e2ba28833841d Mon Sep 17 00:00:00 2001 From: Carlos Alcaide Date: Fri, 16 Jul 2021 13:55:33 -0700 Subject: [PATCH 2/5] =?UTF-8?q?test:=20=F0=9F=92=8D=20Add=20test=20cases?= =?UTF-8?q?=20to=20credential=20stores=20route?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add new test cases to credential stores route --- .../acceptance/credential-stores-test.js | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/ui/admin/tests/acceptance/credential-stores-test.js b/ui/admin/tests/acceptance/credential-stores-test.js index b62c0f3fe..4a8f46177 100644 --- a/ui/admin/tests/acceptance/credential-stores-test.js +++ b/ui/admin/tests/acceptance/credential-stores-test.js @@ -1,13 +1,16 @@ import { module, test } from 'qunit'; -import { visit, currentURL } from '@ember/test-helpers'; +import { visit, currentURL, click, fillIn } 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'; module('Acceptance | credential-stores', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); + let getCredentialStoresCount; + const instances = { scopes: { global: null, @@ -32,17 +35,40 @@ module('Acceptance | credential-stores', function (hooks) { type: 'project', scope: { id: instances.scopes.org.id, type: 'org' }, }); + instances.credentialStore = this.server.create('credential-store', { + 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.unknownCredentialStore = `${urls.credentialStores}/foo`; + urls.newCredentialStore = `${urls.credentialStores}/new`; + // Generate resource counter + getCredentialStoresCount = () => { + return this.server.schema.credentialStores.all().models.length; + }; + authenticateSession({}); }); test('visiting /credential-stores', async function (assert) { - assert.expect(1); + assert.expect(2); await visit(urls.credentialStores); await a11yAudit(); assert.equal(currentURL(), urls.credentialStores); + await visit(urls.credentialStore); + await a11yAudit(); + assert.equal(currentURL(), urls.credentialStore); + }); + + test('can create a new credential store', async function (assert) { + assert.expect(1); + const count = getCredentialStoresCount(); + await visit(urls.newCredentialStore); + await fillIn('[name="name"]', 'random string'); + await click('[type="submit"]'); + assert.equal(getCredentialStoresCount(), count + 1); }); }); From 44510ea8d4d601e1bff600ac4a43cf32fb397605 Mon Sep 17 00:00:00 2001 From: Carlos Alcaide Date: Mon, 19 Jul 2021 14:16:40 -0700 Subject: [PATCH 3/5] =?UTF-8?q?test:=20=F0=9F=92=8D=20Adding=20integration?= =?UTF-8?q?=20test=20for=20credential=20stores?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adding more integration test cases for credential stores --- .../acceptance/credential-stores-test.js | 145 +++++++++++++++++- 1 file changed, 143 insertions(+), 2 deletions(-) diff --git a/ui/admin/tests/acceptance/credential-stores-test.js b/ui/admin/tests/acceptance/credential-stores-test.js index 4a8f46177..1a79f6bc5 100644 --- a/ui/admin/tests/acceptance/credential-stores-test.js +++ b/ui/admin/tests/acceptance/credential-stores-test.js @@ -1,5 +1,5 @@ import { module, test } from 'qunit'; -import { visit, currentURL, click, fillIn } from '@ember/test-helpers'; +import { visit, currentURL, find, click, fillIn } 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'; @@ -21,7 +21,12 @@ module('Acceptance | credential-stores', function (hooks) { const urls = { globalScope: null, + orgScope: null, + projectScope: null, credentialStores: null, + credentialStore: null, + unknownCredentialStore: null, + newCredentialStore: null, }; hooks.beforeEach(function () { @@ -63,7 +68,14 @@ module('Acceptance | credential-stores', function (hooks) { assert.equal(currentURL(), urls.credentialStore); }); - test('can create a new credential store', async function (assert) { + test.skip('visiting an unknown credential store display 404 message', async function (assert) { + assert.expect(1); + await visit(urls.unknownCredentialStore); + await a11yAudit(); + assert.ok(find('.rose-message-subtitle').textContent.trim(), 'Error 404'); + }); + + test('can create a new credential stores', async function (assert) { assert.expect(1); const count = getCredentialStoresCount(); await visit(urls.newCredentialStore); @@ -71,4 +83,133 @@ module('Acceptance | credential-stores', function (hooks) { await click('[type="submit"]'); assert.equal(getCredentialStoresCount(), count + 1); }); + + test('can cancel create new credential stores', async function (assert) { + assert.expect(2); + const count = getCredentialStoresCount(); + await visit(urls.newCredentialStore); + await fillIn('[name="name"]', 'random string'); + await click('.rose-form-actions [type="button"]'); + assert.equal(currentURL(), urls.credentialStores); + assert.equal(getCredentialStoresCount(), count); + }); + + // Skip this test for now. The request is successful + // but we are expecting to not be. + test.skip('saving a new credential store with invalid fields displays error messages', async function (assert) { + assert.expect(2); + this.server.post('/credential-stores', () => { + 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.newCredentialStore); + await click('form [type="submit"]', 'Click new'); // Issue starts here + 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 save changes to existing credential store', async function (assert) { + assert.expect(3); + assert.notEqual(instances.credentialStore.name, 'random string'); + await visit(urls.credentialStore); + await click('form [type="button"]', 'Activate edit mode'); + await fillIn('[name="name"]', 'random string'); + await click('.rose-form-actions [type="submit"]'); + assert.equal(currentURL(), urls.credentialStore); + assert.equal( + this.server.schema.credentialStores.all().models[0].name, + 'random string' + ); + }); + + test('can cancel changes to existing credential store', async function (assert) { + assert.expect(2); + await visit(urls.credentialStore); + await click('form [type="button"]', 'Activate edit mode'); + await fillIn('[name="name"]', 'random string'); + await click('.rose-form-actions [type="button"]'); + assert.notEqual(instances.credentialStore.name, 'random string'); + assert.equal(find('[name="name"]').value, instances.credentialStore.name); + }); + + // Skip this test for now. The request is successful + // but we are expecting to not be. + test.skip('saving an existing credential store with invalid fields displays error messages', async function (assert) { + assert.expect(2); + this.server.patch('/credential-stores/:id', () => { + 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.credentialStore); + await click('form [type="button"]', 'Activate edit mode'); + await fillIn('[name="name"]', 'random string'); + 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.skip('can discard unsaved credential store changes via dialog', async function (assert) { + assert.expect(5); + const confirmService = this.owner.lookup('service:confirm'); + confirmService.enabled = true; + assert.notEqual(instances.credentialStore.name, 'random string'); + + await visit(urls.credentialStore); + await click('form [type="button"]', 'Activate edit mode'); + assert.equal(currentURL(), urls.credentialStore); + + try { + await visit(urls.credentialStores); + } catch (e) { + assert.ok(find('.rose-dialog')); + await click('.rose-dialog-footer button:first-child'); + assert.equal(currentURL(), urls.credentialStores); + assert.notEqual( + this.server.schema.credentialStores.all().models[0].name, + 'random string' + ); + } + }); }); From f69477e55647b80b2f8017f71519149d5c7b5555 Mon Sep 17 00:00:00 2001 From: Carlos Alcaide Date: Tue, 20 Jul 2021 10:36:34 -0700 Subject: [PATCH 4/5] =?UTF-8?q?test:=20=F0=9F=92=8D=20Add=20credential=20s?= =?UTF-8?q?tores=20create=20and=20edit=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add credential store create and edit acceptance tests --- .../form/credential-store/index.hbs | 27 +++++++++++- .../acceptance/credential-stores-test.js | 41 +++++++++++++++---- 2 files changed, 57 insertions(+), 11 deletions(-) diff --git a/ui/admin/app/components/form/credential-store/index.hbs b/ui/admin/app/components/form/credential-store/index.hbs index f972854c1..b70183ed3 100644 --- a/ui/admin/app/components/form/credential-store/index.hbs +++ b/ui/admin/app/components/form/credential-store/index.hbs @@ -2,6 +2,7 @@ @onSubmit={{@submit}} @cancel={{@cancel}} @disabled={{@model.isSaving}} + @error={{@model.errors.base}} @showEditToggle={{if @model.isNew false true}} as |form| > @@ -18,16 +19,38 @@ @value={{@model.name}} @label={{t 'form.name.label'}} @helperText={{t 'form.name.help'}} + @error={{@model.errors.name}} readonly={{false}} - /> + as |field| + > + {{#if @model.errors.name}} + + {{#each @model.errors.name as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + disabled={{@model.isSaving}} + readonly={{false}} + as |field| + > + {{#if @model.errors.description}} + + {{#each @model.errors.description as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + { return new Response( @@ -118,7 +119,7 @@ module('Acceptance | credential-stores', function (hooks) { ); }); await visit(urls.newCredentialStore); - await click('form [type="submit"]', 'Click new'); // Issue starts here + await click('[type="submit"]'); assert.ok( find('[role="alert"]').textContent.trim(), 'The request was invalid.' @@ -153,9 +154,7 @@ module('Acceptance | credential-stores', function (hooks) { assert.equal(find('[name="name"]').value, instances.credentialStore.name); }); - // Skip this test for now. The request is successful - // but we are expecting to not be. - test.skip('saving an existing credential store with invalid fields displays error messages', async function (assert) { + test('saving an existing credential store with invalid fields displays error messages', async function (assert) { assert.expect(2); this.server.patch('/credential-stores/:id', () => { return new Response( @@ -190,7 +189,7 @@ module('Acceptance | credential-stores', function (hooks) { ); }); - test.skip('can discard unsaved credential store changes via dialog', async function (assert) { + test('can discard unsaved credential store changes via dialog', async function (assert) { assert.expect(5); const confirmService = this.owner.lookup('service:confirm'); confirmService.enabled = true; @@ -198,13 +197,14 @@ module('Acceptance | credential-stores', function (hooks) { await visit(urls.credentialStore); await click('form [type="button"]', 'Activate edit mode'); + await fillIn('[name="name"]', 'random string'); assert.equal(currentURL(), urls.credentialStore); try { await visit(urls.credentialStores); } catch (e) { assert.ok(find('.rose-dialog')); - await click('.rose-dialog-footer button:first-child'); + await click('.rose-dialog-footer button:first-child', 'Click Discard'); assert.equal(currentURL(), urls.credentialStores); assert.notEqual( this.server.schema.credentialStores.all().models[0].name, @@ -212,4 +212,27 @@ module('Acceptance | credential-stores', function (hooks) { ); } }); + + test('can cancel discard unsaved credential store via dialog', async function (assert) { + assert.expect(5); + const confirmService = this.owner.lookup('service:confirm'); + confirmService.enabled = true; + assert.notEqual(instances.credentialStore.name, 'random string'); + await visit(urls.credentialStore); + await click('form [type="button"]', 'Activate edit mode'); + await fillIn('[name="name"]', 'random string'); + assert.equal(currentURL(), urls.credentialStore); + + try { + await visit(urls.credentialStores); + } catch (e) { + assert.ok(find('.rose-dialog')); + await click('.rose-dialog-footer button:last-child'); + assert.equal(currentURL(), urls.credentialStore); + assert.notEqual( + this.server.schema.credentialStores.all().models[0].name, + 'random string' + ); + } + }); }); From d96d7425f8d91f949f4aec20594c7ccc15c185bf Mon Sep 17 00:00:00 2001 From: Carlos Alcaide Date: Wed, 21 Jul 2021 10:36:16 -0700 Subject: [PATCH 5/5] Fix skip test with false positive --- ui/admin/tests/acceptance/credential-stores-test.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ui/admin/tests/acceptance/credential-stores-test.js b/ui/admin/tests/acceptance/credential-stores-test.js index 7507f5ba8..f41d31a12 100644 --- a/ui/admin/tests/acceptance/credential-stores-test.js +++ b/ui/admin/tests/acceptance/credential-stores-test.js @@ -69,13 +69,14 @@ module('Acceptance | credential-stores', function (hooks) { assert.equal(currentURL(), urls.credentialStore); }); - // This test is returning an issue regarding not finding resource. - // It is a false positive. - test.skip('visiting an unknown credential store display 404 message', async function (assert) { + test('visiting an unknown credential store display 404 message', async function (assert) { assert.expect(1); await visit(urls.unknownCredentialStore); await a11yAudit(); - assert.ok(find('.rose-message-subtitle').textContent.trim(), 'Error 404'); + assert.equal( + find('.rose-message-subtitle').textContent.trim(), + 'Error 404' + ); }); test('can create a new credential stores', async function (assert) {