From 4267839b96c8b629e39cd8df9ffef62c4c8cd4c0 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Sat, 3 Oct 2020 11:08:50 -0400 Subject: [PATCH 01/19] test: reduce branching in authenticator --- ui/core/app/authenticators/password.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/core/app/authenticators/password.js b/ui/core/app/authenticators/password.js index a0ceccdd9..43a9c8fb7 100644 --- a/ui/core/app/authenticators/password.js +++ b/ui/core/app/authenticators/password.js @@ -63,7 +63,7 @@ export default class PasswordAuthenticator extends BasePasswordAuthenticator { authenticate() { return super.authenticate(...arguments).then((data) => { const token = data?.token; - if (token) this.addTokenToAuthorization(token); + this.addTokenToAuthorization(token); return data; }); } @@ -93,6 +93,6 @@ export default class PasswordAuthenticator extends BasePasswordAuthenticator { const headers = adapterPrototype?.headers; if (!headers) adapterPrototype.headers = {}; adapterPrototype.headers.Authorization = null; - if (token) adapterPrototype.headers.Authorization = `Bearer ${token}`; + adapterPrototype.headers.Authorization = `Bearer ${token}`; } } From ee9bb281c1bf09255ca52e3414a80ee0540c29db Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Sat, 3 Oct 2020 11:19:03 -0400 Subject: [PATCH 02/19] chore: remove duplicate tests --- .../host-catalogs/host-sets-test.js | 132 ------------------ .../acceptance/host-catalogs/hosts-test.js | 132 ------------------ ui/core/tests/acceptance/host-sets-test.js | 54 ++++++- ui/core/tests/acceptance/hosts-test.js | 54 ++++++- 4 files changed, 106 insertions(+), 266 deletions(-) delete mode 100644 ui/core/tests/acceptance/host-catalogs/host-sets-test.js delete mode 100644 ui/core/tests/acceptance/host-catalogs/hosts-test.js diff --git a/ui/core/tests/acceptance/host-catalogs/host-sets-test.js b/ui/core/tests/acceptance/host-catalogs/host-sets-test.js deleted file mode 100644 index d7cd8a32a..000000000 --- a/ui/core/tests/acceptance/host-catalogs/host-sets-test.js +++ /dev/null @@ -1,132 +0,0 @@ -import { module, test } from 'qunit'; -import { visit, currentURL, fillIn, click, find, findAll } 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 { Response } from 'miragejs'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; - -module('Acceptance | host catalogs | host sets', function (hooks) { - setupApplicationTest(hooks); - setupMirage(hooks); - - const instances = { - scopes: { - global: null, - org: null, - project: null, - }, - hostCatalog: null, - hostSets: null, - }; - const urls = { - orgScope: null, - projects: null, - project: null, - hostCatalogs: null, - hostCatalog: null, - hostSets: null, - newHostSet: null - }; - let hostSetsCount; - - hooks.beforeEach(function () { - // Setup Mirage mock resources for this test - authenticateSession(); - 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.hostCatalog = this.server.create('host-catalog', { - scope: instances.scopes.project, - }); - instances.hostSets = this.server.createList('host-set', 3, { - scope: instances.scopes.project, - hostCatalog: instances.hostCatalog - }); - hostSetsCount = instances.hostCatalog.hostSetIds.length; - // Generate route URLs for resources - urls.orgScope = `/scopes/${instances.scopes.org.id}`; - urls.projects = `${urls.orgScope}/projects`; - urls.project = `${urls.projects}/${instances.hostCatalog.scope.id}`; - urls.hostCatalogs = `${urls.project}/host-catalogs`; - urls.hostCatalog = `${urls.hostCatalogs}/${instances.hostCatalog.id}`; - urls.hostSets = `${urls.hostCatalog}/host-sets`; - urls.newHostSet = `${urls.hostSets}/new`; - }); - - test('visiting host sets', async function (assert) { - assert.expect(3); - await visit(urls.hostSets); - await a11yAudit(); - assert.equal(currentURL(), urls.hostSets); - assert.ok(hostSetsCount); - assert.equal(findAll('tbody tr').length, hostSetsCount); - }); - - test('can navigate to a host set form', async function (assert) { - assert.expect(1); - await visit(urls.hostSets); - await click('main tbody .rose-table-header-cell:nth-child(1) a'); - await a11yAudit(); - assert.notEqual(currentURL(), urls.hostSets); - }); - - test('can create host set and save changes', async function (assert) { - assert.expect(1); - await visit(urls.newHostSet); - await fillIn('[name="name"]', 'Test Name'); - await fillIn('[name="description"]', 'description'); - await click('form [type="submit"]:not(:disabled)'); - await visit(urls.hostSets); - assert.equal(findAll('tbody tr').length, hostSetsCount + 1); - }); - - test('can create host set and cancel changes', async function (assert) { - assert.expect(2); - await visit(urls.newHostSet); - await fillIn('[name="name"]', 'Test Name'); - await click('form button:not([type="submit"])'); - assert.equal(currentURL(), urls.hostSets); - assert.equal(findAll('tbody tr').length, hostSetsCount); - }); - - test('saving a new host set with invalid fields displays error messages', async function (assert) { - assert.expect(2); - this.server.post('/host-sets', () => { - 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.newHostSet); - await fillIn('[name="name"]', 'new target'); - await click('form [type="submit"]'); - await a11yAudit(); - assert.ok(find('[role="alert"]')); - assert.ok(find('.rose-form-error-message')); - }); -}); diff --git a/ui/core/tests/acceptance/host-catalogs/hosts-test.js b/ui/core/tests/acceptance/host-catalogs/hosts-test.js deleted file mode 100644 index 565ef41a8..000000000 --- a/ui/core/tests/acceptance/host-catalogs/hosts-test.js +++ /dev/null @@ -1,132 +0,0 @@ -import { module, test } from 'qunit'; -import { visit, currentURL, fillIn, click, find, findAll } 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 { Response } from 'miragejs'; -import { - authenticateSession, - // These are left here intentionally for future reference. - //currentSession, - //invalidateSession, -} from 'ember-simple-auth/test-support'; - -module('Acceptance | host catalogs | hosts', function (hooks) { - setupApplicationTest(hooks); - setupMirage(hooks); - - const instances = { - scopes: { - global: null, - org: null, - project: null, - }, - hostCatalog: null, - hosts: null, - }; - const urls = { - orgScope: null, - projects: null, - project: null, - hostCatalogs: null, - hostCatalog: null, - hosts: null, - newHost: null - }; - let hostsCount; - - hooks.beforeEach(function () { - // Setup Mirage mock resources for this test - authenticateSession(); - 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.hostCatalog = this.server.create('host-catalog', { - scope: instances.scopes.project, - }); - instances.hosts = this.server.createList('host', 3, { - scope: instances.scopes.project, - hostCatalog: instances.hostCatalog - }); - hostsCount = instances.hosts.length; - // Generate route URLs for resources - urls.orgScope = `/scopes/${instances.scopes.org.id}`; - urls.projects = `${urls.orgScope}/projects`; - urls.project = `${urls.projects}/${instances.hostCatalog.scope.id}`; - urls.hostCatalogs = `${urls.project}/host-catalogs`; - urls.hostCatalog = `${urls.hostCatalogs}/${instances.hostCatalog.id}`; - urls.hosts = `${urls.hostCatalog}/hosts`; - urls.newHost = `${urls.hosts}/new`; - }); - - test('visiting hosts', async function (assert) { - assert.expect(3); - await visit(urls.hosts); - await a11yAudit(); - assert.equal(currentURL(), urls.hosts); - assert.ok(hostsCount); - assert.equal(findAll('tbody tr').length, hostsCount); - }); - - test('can navigate to a host form', async function (assert) { - assert.expect(1); - await visit(urls.hosts); - await click('main tbody .rose-table-header-cell:nth-child(1) a'); - await a11yAudit(); - assert.notEqual(currentURL(), urls.hosts); - }); - - test('can create host and save changes', async function (assert) { - assert.expect(1); - await visit(urls.newHost); - await fillIn('[name="name"]', 'Test Name'); - await fillIn('[name="description"]', 'description'); - await click('form [type="submit"]:not(:disabled)'); - await visit(urls.hosts); - assert.equal(findAll('tbody tr').length, hostsCount + 1); - }); - - test('can create host and cancel changes', async function (assert) { - assert.expect(2); - await visit(urls.newHost); - await fillIn('[name="name"]', 'Test Name'); - await click('form button:not([type="submit"])'); - assert.equal(currentURL(), urls.hosts); - assert.equal(findAll('tbody tr').length, hostsCount); - }); - - test('saving a new host with invalid fields displays error messages', async function (assert) { - assert.expect(2); - this.server.post('/hosts', () => { - 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.newHost); - await fillIn('[name="name"]', 'new target'); - await click('form [type="submit"]'); - await a11yAudit(); - assert.ok(find('[role="alert"]')); - assert.ok(find('.rose-form-error-message')); - }); -}); diff --git a/ui/core/tests/acceptance/host-sets-test.js b/ui/core/tests/acceptance/host-sets-test.js index 1fdfc0f91..1b69b06a3 100644 --- a/ui/core/tests/acceptance/host-sets-test.js +++ b/ui/core/tests/acceptance/host-sets-test.js @@ -1,5 +1,5 @@ import { module, test } from 'qunit'; -import { visit, currentURL, fillIn, click, find } from '@ember/test-helpers'; +import { visit, currentURL, fillIn, click, find, findAll } 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'; @@ -32,6 +32,7 @@ module('Acceptance | host sets', function (hooks) { hostCatalog: null, hostSets: null, hostSet: null, + newHostSet: null }; hooks.beforeEach(function () { @@ -61,6 +62,7 @@ module('Acceptance | host sets', function (hooks) { urls.hostCatalog = `${urls.hostCatalogs}/${instances.hostCatalog.id}`; urls.hostSets = `${urls.hostCatalog}/host-sets`; urls.hostSet = `${urls.hostSets}/${instances.hostSet.id}`; + urls.newHostSet = `${urls.hostSets}/new`; }); test('visiting a host set', async function (assert) { @@ -148,4 +150,54 @@ module('Acceptance | host sets', function (hooks) { await a11yAudit(); assert.ok(find('[role="alert"]')); }); + + test('can create host set and save changes', async function (assert) { + assert.expect(1); + const hostSetsCount = this.server.db.hostSets.length; + await visit(urls.newHostSet); + await fillIn('[name="name"]', 'Test Name'); + await fillIn('[name="description"]', 'description'); + await click('form [type="submit"]:not(:disabled)'); + await visit(urls.hostSets); + assert.equal(findAll('tbody tr').length, hostSetsCount + 1); + }); + + test('can create host set and cancel changes', async function (assert) { + assert.expect(2); + const hostSetsCount = this.server.db.hostSets.length; + await visit(urls.newHostSet); + await fillIn('[name="name"]', 'Test Name'); + await click('form button:not([type="submit"])'); + assert.equal(currentURL(), urls.hostSets); + assert.equal(findAll('tbody tr').length, hostSetsCount); + }); + + test('saving a new host set with invalid fields displays error messages', async function (assert) { + assert.expect(2); + this.server.post('/host-sets', () => { + 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.newHostSet); + await fillIn('[name="name"]', 'new target'); + await click('form [type="submit"]'); + await a11yAudit(); + assert.ok(find('[role="alert"]')); + assert.ok(find('.rose-form-error-message')); + }); }); diff --git a/ui/core/tests/acceptance/hosts-test.js b/ui/core/tests/acceptance/hosts-test.js index 2514210fb..d04904da9 100644 --- a/ui/core/tests/acceptance/hosts-test.js +++ b/ui/core/tests/acceptance/hosts-test.js @@ -1,5 +1,5 @@ import { module, test } from 'qunit'; -import { visit, currentURL, fillIn, click, find } from '@ember/test-helpers'; +import { visit, currentURL, fillIn, click, find, findAll } 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'; @@ -32,6 +32,7 @@ module('Acceptance | hosts', function (hooks) { hostCatalog: null, hosts: null, host: null, + newHost: null }; hooks.beforeEach(function () { @@ -61,6 +62,7 @@ module('Acceptance | hosts', function (hooks) { urls.hostCatalog = `${urls.hostCatalogs}/${instances.hostCatalog.id}`; urls.hosts = `${urls.hostCatalog}/hosts`; urls.host = `${urls.hosts}/${instances.host.id}`; + urls.newHost = `${urls.hosts}/new`; }); test('visiting a host', async function (assert) { @@ -150,4 +152,54 @@ module('Acceptance | hosts', function (hooks) { await a11yAudit(); assert.ok(find('[role="alert"]')); }); + + test('can create host and save changes', async function (assert) { + assert.expect(1); + const hostsCount = this.server.db.hosts.length; + await visit(urls.newHost); + await fillIn('[name="name"]', 'Test Name'); + await fillIn('[name="description"]', 'description'); + await click('form [type="submit"]:not(:disabled)'); + await visit(urls.hosts); + assert.equal(findAll('tbody tr').length, hostsCount + 1); + }); + + test('can create host and cancel changes', async function (assert) { + assert.expect(2); + const hostsCount = this.server.db.hosts.length; + await visit(urls.newHost); + await fillIn('[name="name"]', 'Test Name'); + await click('form button:not([type="submit"])'); + assert.equal(currentURL(), urls.hosts); + assert.equal(findAll('tbody tr').length, hostsCount); + }); + + test('saving a new host with invalid fields displays error messages', async function (assert) { + assert.expect(2); + this.server.post('/hosts', () => { + 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.newHost); + await fillIn('[name="name"]', 'new target'); + await click('form [type="submit"]'); + await a11yAudit(); + assert.ok(find('[role="alert"]')); + assert.ok(find('.rose-form-error-message')); + }); }); From c92f66c7c65f38f85c59f9237c3037c8ac320bbc Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Sat, 3 Oct 2020 11:36:30 -0400 Subject: [PATCH 03/19] chore: add test case for global-scope-cannot-be-fetched --- ui/core/tests/acceptance/orgs-test.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/ui/core/tests/acceptance/orgs-test.js b/ui/core/tests/acceptance/orgs-test.js index aaeca8356..532494519 100644 --- a/ui/core/tests/acceptance/orgs-test.js +++ b/ui/core/tests/acceptance/orgs-test.js @@ -56,6 +56,21 @@ module('Acceptance | org', function (hooks) { assert.equal(findAll('article').length, orgsCount); }); + test('visiting orgs is successful even when the global scope cannot be fetched', async function (assert) { + assert.expect(3); + this.server.get('/scopes/:id', ({ scopes }, { params: { id } }) => { + const scope = scopes.find(id); + return (id === 'global') + ? new Response(404) + : scope; + }); + await visit(urls.orgs); + await a11yAudit(); + assert.ok(orgsCount); + assert.equal(currentURL(), urls.orgs); + assert.equal(findAll('article').length, orgsCount); + }); + test('visiting scopes redirects to index', async function (assert) { assert.expect(1); await visit(urls.scopes); From d4697d329bd513ee1ce2469f2d0f9c483825880d Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Sat, 3 Oct 2020 12:00:25 -0400 Subject: [PATCH 04/19] chore: add missing default test of error message component --- .../components/error/message-test.js | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/ui/core/tests/integration/components/error/message-test.js b/ui/core/tests/integration/components/error/message-test.js index 2671c9b7e..c33db71b8 100644 --- a/ui/core/tests/integration/components/error/message-test.js +++ b/ui/core/tests/integration/components/error/message-test.js @@ -20,6 +20,32 @@ module('Integration | Component | error/message', function (hooks) { find('.rose-message-description').textContent.trim(), 'You are not authenticated. Please authenticate and try again later.' ); + await render(hbs``); + assert.equal( + find('.rose-message-title').textContent.trim(), + 'You are not authorized' + ); + assert.equal( + find('.rose-message-subtitle').textContent.trim(), + 'Error 403' + ); + assert.equal( + find('.rose-message-description').textContent.trim(), + 'You must be granted permissions to view this data. Ask your administrator if you think you should have access.' + ); + await render(hbs``); + assert.equal( + find('.rose-message-title').textContent.trim(), + 'Resource not found' + ); + assert.equal( + find('.rose-message-subtitle').textContent.trim(), + 'Error 404' + ); + assert.equal( + find('.rose-message-description').textContent.trim(), + 'We could not find the requested resource. You can ask your administrator or try again later.' + ); }); test('it renders default error for unknown error status', async function (assert) { From bc3cb20a439ddd640064adfd5e697fd2a2a9bc29 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Sat, 3 Oct 2020 12:16:54 -0400 Subject: [PATCH 05/19] chore: full coverage for orgs route --- ui/core/app/routes/scopes/scope/orgs.js | 8 +++----- ui/core/tests/acceptance/authentication-test.js | 7 +++++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/ui/core/app/routes/scopes/scope/orgs.js b/ui/core/app/routes/scopes/scope/orgs.js index 76ff7b208..626de80fa 100644 --- a/ui/core/app/routes/scopes/scope/orgs.js +++ b/ui/core/app/routes/scopes/scope/orgs.js @@ -38,9 +38,8 @@ export default class ScopesScopeOrgsRoute extends Route { */ @action cancel(org) { - const { isNew } = org; org.rollbackAttributes(); - if (isNew) this.transitionTo('scopes.scope.orgs'); + this.transitionTo('scopes.scope.orgs'); } /** @@ -51,13 +50,12 @@ export default class ScopesScopeOrgsRoute extends Route { @action @loading async save(org) { - const { isNew } = org; try { await org.save(); - await this.transitionTo('scopes.scope', org); + await this.transitionTo('scopes.scope.projects', org); this.refresh(); this.notify.success( - this.intl.t(isNew ? 'notifications.create-success' : 'notifications.save-success') + this.intl.t('notifications.create-success') ); } catch (error) { // TODO: replace with translated strings diff --git a/ui/core/tests/acceptance/authentication-test.js b/ui/core/tests/acceptance/authentication-test.js index 9a6641194..1816d1ae0 100644 --- a/ui/core/tests/acceptance/authentication-test.js +++ b/ui/core/tests/acceptance/authentication-test.js @@ -105,6 +105,13 @@ module('Acceptance | authentication', function (hooks) { assert.notOk(currentSession().isAuthenticated); }); + test('visiting orgs while unauthenticated redirects to first global authenticate method', async function (assert) { + assert.expect(2); + await visit(orgsURL); + assert.equal(currentURL(), authMethodGlobalAuthenticateURL); + assert.notOk(currentSession().isAuthenticated); + }); + test('visiting projects while unauthenticated redirects to first global authenticate method', async function (assert) { assert.expect(2); await visit(projectsURL); From 685b291a5207e93a416aadb0f34c6613c829a6c2 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Sat, 3 Oct 2020 12:30:08 -0400 Subject: [PATCH 06/19] chore: add missing unauthenticated redirect tests --- .../tests/acceptance/authentication-test.js | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/ui/core/tests/acceptance/authentication-test.js b/ui/core/tests/acceptance/authentication-test.js index 1816d1ae0..b5a4ba249 100644 --- a/ui/core/tests/acceptance/authentication-test.js +++ b/ui/core/tests/acceptance/authentication-test.js @@ -39,6 +39,10 @@ module('Acceptance | authentication', function (hooks) { let authMethodGlobalAuthenticateURL; let orgsURL; let projectsURL; + let usersURL; + let groupsURL; + let rolesURL; + let authMethodsURL; hooks.beforeEach(function () { invalidateSession(); @@ -68,6 +72,10 @@ module('Acceptance | authentication', function (hooks) { authMethodAuthenticateURL = `/scopes/${orgScopeID}/authenticate/${authMethodID}`; orgsURL = `/scopes/global/orgs`; projectsURL = `/scopes/${orgScopeID}/projects`; + usersURL = `/scopes/${orgScopeID}/users`; + groupsURL = `/scopes/${orgScopeID}/groups`; + rolesURL = `/scopes/${orgScopeID}/roles`; + authMethodsURL = `/scopes/${orgScopeID}/auth-methods`; }); test('visiting auth methods authenticate route redirects to first auth method', async function (assert) { @@ -119,6 +127,34 @@ module('Acceptance | authentication', function (hooks) { assert.notOk(currentSession().isAuthenticated); }); + test('visiting users while unauthenticated redirects to first global authenticate method', async function (assert) { + assert.expect(2); + await visit(usersURL); + assert.equal(currentURL(), authMethodGlobalAuthenticateURL); + assert.notOk(currentSession().isAuthenticated); + }); + + test('visiting roles while unauthenticated redirects to first global authenticate method', async function (assert) { + assert.expect(2); + await visit(rolesURL); + assert.equal(currentURL(), authMethodGlobalAuthenticateURL); + assert.notOk(currentSession().isAuthenticated); + }); + + test('visiting groups while unauthenticated redirects to first global authenticate method', async function (assert) { + assert.expect(2); + await visit(groupsURL); + assert.equal(currentURL(), authMethodGlobalAuthenticateURL); + assert.notOk(currentSession().isAuthenticated); + }); + + test('visiting auth methods while unauthenticated redirects to first global authenticate method', async function (assert) { + assert.expect(2); + await visit(authMethodsURL); + assert.equal(currentURL(), authMethodGlobalAuthenticateURL); + assert.notOk(currentSession().isAuthenticated); + }); + test('visiting auth method authenticate route', async function (assert) { assert.expect(1); await visit(authMethodAuthenticateURL); From 0bb884b19ef80ff708297661ef108f0e004e4fd4 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Sat, 3 Oct 2020 12:37:03 -0400 Subject: [PATCH 07/19] chore: add test for authenticating in global scope --- ui/core/tests/acceptance/authentication-test.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/ui/core/tests/acceptance/authentication-test.js b/ui/core/tests/acceptance/authentication-test.js index b5a4ba249..113a78c07 100644 --- a/ui/core/tests/acceptance/authentication-test.js +++ b/ui/core/tests/acceptance/authentication-test.js @@ -202,7 +202,19 @@ module('Acceptance | authentication', function (hooks) { assert.notOk(currentSession().isAuthenticated); }); - test('successful authentication redirects to projects', async function (assert) { + test('successful authentication with the global scope redirects to orgs', async function (assert) { + assert.expect(4); + await visit(authMethodGlobalAuthenticateURL); + assert.notOk(currentSession().isAuthenticated); + assert.equal(currentURL(), authMethodGlobalAuthenticateURL); + await fillIn('[name="identification"]', 'test'); + await fillIn('[name="password"]', 'test'); + await click('[type="submit"]'); + assert.equal(currentURL(), orgsURL); + assert.ok(currentSession().isAuthenticated); + }); + + test('successful authentication with an org scope redirects to projects', async function (assert) { assert.expect(4); await visit(authMethodAuthenticateURL); assert.notOk(currentSession().isAuthenticated); From 5254cfe228e5509412c567595a5ade50d8d2ba9c Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Sat, 3 Oct 2020 12:43:12 -0400 Subject: [PATCH 08/19] chore: remove branching --- .../controllers/scopes/scope/authenticate.js | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/ui/core/app/controllers/scopes/scope/authenticate.js b/ui/core/app/controllers/scopes/scope/authenticate.js index 139f77c05..5c36ec58e 100644 --- a/ui/core/app/controllers/scopes/scope/authenticate.js +++ b/ui/core/app/controllers/scopes/scope/authenticate.js @@ -1,19 +1,17 @@ import Controller from '@ember/controller'; -import { sort } from '@ember/object/computed'; export default class ScopesScopeAuthenticateController extends Controller { // =attributes /** + * Moves the global scope to index 0. * @type {Array} */ - @sort('model.scopes', (a, b) => { - if (a.isGlobal) { - return -1; - } else if (b.isGlobal) { - return 1; - } - return 0; - }) - sortedScopes; + get sortedScopes() { + return [ + ...this.model.scopes.filter((scope) => scope.id === 'global'), + ...this.model.scopes.filter((scope) => scope.id !== 'global'), + ] + } + } From 7021e3e0f4175094e21bbee188fedc606d92311a Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Sat, 3 Oct 2020 12:46:21 -0400 Subject: [PATCH 09/19] chore: reduce branching in authenticator --- ui/core/app/authenticators/password.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/core/app/authenticators/password.js b/ui/core/app/authenticators/password.js index 43a9c8fb7..f9d97033e 100644 --- a/ui/core/app/authenticators/password.js +++ b/ui/core/app/authenticators/password.js @@ -77,7 +77,7 @@ export default class PasswordAuthenticator extends BasePasswordAuthenticator { */ restore(data) { const token = data?.token; - if (token) this.addTokenToAuthorization(token); + this.addTokenToAuthorization(token); return super.restore(data); } From f7f2770dcad2d9852891e0ec1554dbb79cb3b5c6 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Sat, 3 Oct 2020 12:51:47 -0400 Subject: [PATCH 10/19] chore: add 404 error test --- ui/core/tests/acceptance/orgs-test.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ui/core/tests/acceptance/orgs-test.js b/ui/core/tests/acceptance/orgs-test.js index 532494519..4215d3db7 100644 --- a/ui/core/tests/acceptance/orgs-test.js +++ b/ui/core/tests/acceptance/orgs-test.js @@ -56,6 +56,14 @@ module('Acceptance | org', function (hooks) { assert.equal(findAll('article').length, orgsCount); }); + test('visiting orgs when endpoint 404s shows an error message', async function (assert) { + assert.expect(1); + this.server.get('/scopes', () => new Response(404)); + await visit(urls.orgs); + await a11yAudit(); + assert.ok(find('.rose-message')); + }); + test('visiting orgs is successful even when the global scope cannot be fetched', async function (assert) { assert.expect(3); this.server.get('/scopes/:id', ({ scopes }, { params: { id } }) => { From 87e1fd363e12eccefcee1bd17ffd218d63e8c499 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Sat, 3 Oct 2020 12:57:21 -0400 Subject: [PATCH 11/19] chore: add unauthenticated redirect test --- ui/core/tests/acceptance/authentication-test.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ui/core/tests/acceptance/authentication-test.js b/ui/core/tests/acceptance/authentication-test.js index 113a78c07..d8bdb6162 100644 --- a/ui/core/tests/acceptance/authentication-test.js +++ b/ui/core/tests/acceptance/authentication-test.js @@ -37,6 +37,7 @@ module('Acceptance | authentication', function (hooks) { let authenticateURL; let authMethodAuthenticateURL; let authMethodGlobalAuthenticateURL; + let changePasswordURL; let orgsURL; let projectsURL; let usersURL; @@ -70,6 +71,7 @@ module('Acceptance | authentication', function (hooks) { authenticateURL = `/scopes/${orgScopeID}/authenticate`; authMethodGlobalAuthenticateURL = `/scopes/global/authenticate/${globalAuthMethodID}`; authMethodAuthenticateURL = `/scopes/${orgScopeID}/authenticate/${authMethodID}`; + changePasswordURL = `/account/change-password`; orgsURL = `/scopes/global/orgs`; projectsURL = `/scopes/${orgScopeID}/projects`; usersURL = `/scopes/${orgScopeID}/users`; @@ -113,6 +115,13 @@ module('Acceptance | authentication', function (hooks) { assert.notOk(currentSession().isAuthenticated); }); + test('visiting change password while unauthenticated redirects to first global authenticate method', async function (assert) { + assert.expect(2); + await visit(changePasswordURL); + assert.equal(currentURL(), authMethodGlobalAuthenticateURL); + assert.notOk(currentSession().isAuthenticated); + }); + test('visiting orgs while unauthenticated redirects to first global authenticate method', async function (assert) { assert.expect(2); await visit(orgsURL); From 90247f43a4013e4bd5ac4b01791b03692142199a Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Sat, 3 Oct 2020 13:49:38 -0400 Subject: [PATCH 12/19] =?UTF-8?q?test:=20=F0=9F=92=8D=20add=20sessions=20a?= =?UTF-8?q?cceptance=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../scopes/scope/projects/project/sessions.js | 3 +- ui/core/config/environment.js | 3 + ui/core/tests/acceptance/sessions-test.js | 103 ++++++++++++++++++ 3 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 ui/core/tests/acceptance/sessions-test.js diff --git a/ui/core/app/routes/scopes/scope/projects/project/sessions.js b/ui/core/app/routes/scopes/scope/projects/project/sessions.js index d157fe3aa..b537560a7 100644 --- a/ui/core/app/routes/scopes/scope/projects/project/sessions.js +++ b/ui/core/app/routes/scopes/scope/projects/project/sessions.js @@ -4,8 +4,9 @@ import { action } from '@ember/object'; import { all, hash } from 'rsvp'; import { task, timeout } from 'ember-concurrency'; import { A } from '@ember/array'; +import config from '../../../../../config/environment'; -const POLL_TIMEOUT_SECONDS = 2.5; +const POLL_TIMEOUT_SECONDS = config.sessionPollingTimeoutSeconds; export default class ScopesScopeProjectsProjectSessionsRoute extends Route { diff --git a/ui/core/config/environment.js b/ui/core/config/environment.js index a6f794d02..ffb4c36bf 100644 --- a/ui/core/config/environment.js +++ b/ui/core/config/environment.js @@ -34,6 +34,7 @@ module.exports = function (environment) { appName: APP_NAME, notifyTimeout: 4000, + sessionPollingTimeoutSeconds: 2.5, enableConfirmService: true, contentSecurityPolicyHeader: 'Content-Security-Policy', @@ -99,6 +100,8 @@ module.exports = function (environment) { // Notification timeout should be 0 for fast tests ENV.notifyTimeout = 0; + // Session polling timeout should be short + ENV.sessionPollingTimeoutSeconds = 0.25; ENV.enableConfirmService = false; } diff --git a/ui/core/tests/acceptance/sessions-test.js b/ui/core/tests/acceptance/sessions-test.js new file mode 100644 index 000000000..03ae3a78b --- /dev/null +++ b/ui/core/tests/acceptance/sessions-test.js @@ -0,0 +1,103 @@ +import { module, test } from 'qunit'; +import { visit, currentURL, findAll, find, click } 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 { run, later } from '@ember/runloop'; +import { Response } from 'miragejs'; +import { + authenticateSession, + // These are left here intentionally for future reference. + //currentSession, + //invalidateSession, +} from 'ember-simple-auth/test-support'; + +module('Acceptance | sessions', function (hooks) { + setupApplicationTest(hooks); + setupMirage(hooks); + + const instances = { + scopes: { + global: null, + org: null, + }, + sessions: null, + }; + const urls = { + orgScope: null, + sessions: null, + }; + + hooks.beforeEach(function () { + authenticateSession({}); + 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: instances.scopes.org.type }, + }); + this.server.createList('group', 1, { scope: instances.scopes.org }, 'withMembers'); + this.server.createList('host-catalog', 1, { scope: instances.scopes.project }, 'withChildren'); + this.server.createList('target', 1, { scope: instances.scopes.project }, 'withRandomHostSets'); + instances.sessions = this.server.createList('session', 10, { + scope: instances.scopes.project, + }, 'withAssociations'); + urls.sessions = `/scopes/${instances.scopes.org.id}/projects/${instances.scopes.project.id}/sessions`; + }); + + test('visiting sessions', async function (assert) { + assert.expect(2); + authenticateSession({}); + later(async () => { + run.cancelTimers(); + await a11yAudit(); + assert.equal(currentURL(), urls.sessions); + assert.equal(findAll('tbody tr').length, instances.sessions.length); + }, 750); + await visit(urls.sessions); + }); + + test('visiting sessions without users or targets is OK', async function (assert) { + assert.expect(2); + instances.sessions[0].update({ + userId: null, + targetId: null + }); + authenticateSession({}); + later(async () => { + run.cancelTimers(); + await a11yAudit(); + assert.equal(currentURL(), urls.sessions); + assert.equal(findAll('tbody tr').length, instances.sessions.length); + }, 750); + await visit(urls.sessions); + }); + + test('cancelling a session', async function (assert) { + assert.expect(2); + authenticateSession({}); + later(async () => { + run.cancelTimers(); + await click('tbody tr:first-child td:last-child button'); + assert.ok(find('[role="alert"].is-success')); + }, 750); + await visit(urls.sessions); + assert.equal(currentURL(), urls.sessions); + }); + + test('cancelling a session with error shows notification', async function (assert) { + assert.expect(2); + this.server.post('/sessions/:id_method', () => new Response(400)); + authenticateSession({}); + later(async () => { + run.cancelTimers(); + await click('tbody tr:first-child td:last-child button'); + assert.ok(find('[role="alert"].is-error')); + }, 750); + await visit(urls.sessions); + assert.equal(currentURL(), urls.sessions); + }); +}); From 87c8ef1d2f59ed5bc9b8e48a0893587e6cc995c9 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Sat, 3 Oct 2020 13:53:06 -0400 Subject: [PATCH 13/19] chore: cover user account toggle off case --- ui/core/tests/acceptance/users/accounts-test.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ui/core/tests/acceptance/users/accounts-test.js b/ui/core/tests/acceptance/users/accounts-test.js index 8bcaa951a..aa941f5ed 100644 --- a/ui/core/tests/acceptance/users/accounts-test.js +++ b/ui/core/tests/acceptance/users/accounts-test.js @@ -97,8 +97,11 @@ module('Acceptance | users | accounts', function (hooks) { instances.user.update({ accountIds: [] }); await visit(urls.accounts); assert.equal(findAll('tbody tr').length, 0); - await click('.rose-layout-page-actions a') + await click('.rose-layout-page-actions a'); assert.equal(currentURL(), urls.addAccounts); + // Click three times to select, unselect, then reselect (for coverage) + await click('tbody label'); + await click('tbody label'); await click('tbody label'); await click('form [type="submit"]'); await visit(urls.accounts); From 41fc8549587f428bc3f198024027ebb2592ec4f9 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Sat, 3 Oct 2020 13:56:26 -0400 Subject: [PATCH 14/19] chore: cover target host set toggle off case --- ui/core/tests/acceptance/targets-test.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ui/core/tests/acceptance/targets-test.js b/ui/core/tests/acceptance/targets-test.js index a269173b0..76e87a931 100644 --- a/ui/core/tests/acceptance/targets-test.js +++ b/ui/core/tests/acceptance/targets-test.js @@ -265,6 +265,9 @@ module('Acceptance | targets', function (hooks) { assert.equal(findAll('tbody tr').length, 0); await click('.rose-layout-page-actions a') assert.equal(currentURL(), urls.targetAddHostSets); + // Click three times to select, unselect, then reselect (for coverage) + await click('tbody label'); + await click('tbody label'); await click('tbody label'); await click('form [type="submit"]'); await visit(urls.targetHostSets); From 909642eee2c3e68c0b79e8d271940fe9ca5a9d71 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Sat, 3 Oct 2020 13:59:07 -0400 Subject: [PATCH 15/19] chore: cover toggle off cases --- ui/core/tests/acceptance/groups/members-test.js | 5 ++++- ui/core/tests/acceptance/roles/principals-test.js | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ui/core/tests/acceptance/groups/members-test.js b/ui/core/tests/acceptance/groups/members-test.js index 69edf67df..c81154b49 100644 --- a/ui/core/tests/acceptance/groups/members-test.js +++ b/ui/core/tests/acceptance/groups/members-test.js @@ -41,7 +41,7 @@ module('Acceptance | groups | members', function (hooks) { instances.group = this.server.create('group', { scope: instances.scopes.org, }, 'withMembers'); - membersCount = instances.group.memberIds.length; + membersCount = instances.group.memberIds.length; urls.groups = `/scopes/${instances.scopes.org.id}/groups`; urls.group = `${urls.groups}/${instances.group.id}`; urls.members = `${urls.group}/members`; @@ -98,6 +98,9 @@ module('Acceptance | groups | members', function (hooks) { assert.equal(findAll('tbody tr').length, 0); await click('.rose-layout-page-actions a') assert.equal(currentURL(), urls.addMembers); + // Click three times to select, unselect, then reselect (for coverage) + await click('tbody label'); + await click('tbody label'); await click('tbody label'); await click('form [type="submit"]'); await visit(urls.members); diff --git a/ui/core/tests/acceptance/roles/principals-test.js b/ui/core/tests/acceptance/roles/principals-test.js index 493a7a9f4..cebf3031b 100644 --- a/ui/core/tests/acceptance/roles/principals-test.js +++ b/ui/core/tests/acceptance/roles/principals-test.js @@ -90,6 +90,9 @@ module('Acceptance | roles | principals', function (hooks) { assert.equal(findAll('tbody tr').length, 0); await click('.rose-layout-page-actions a') assert.equal(currentURL(), urls.addPrincipals); + // Click three times to select, unselect, then reselect (for coverage) + await click('tbody label'); + await click('tbody label'); await click('tbody label'); await click('form [type="submit"]'); await visit(urls.rolePrincipals); From 6e49fe2bd9594d3e42d81c98af5430241fddd833 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Sat, 3 Oct 2020 14:06:22 -0400 Subject: [PATCH 16/19] chore: pass correct args to form actions --- .../components/form/account/password/set-password/index.hbs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ui/core/app/components/form/account/password/set-password/index.hbs b/ui/core/app/components/form/account/password/set-password/index.hbs index d3a1f6df2..df0ca8215 100644 --- a/ui/core/app/components/form/account/password/set-password/index.hbs +++ b/ui/core/app/components/form/account/password/set-password/index.hbs @@ -26,8 +26,9 @@ - \ No newline at end of file + From a6de02de07d9bd4d485109d6e88855f111d21aa0 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Sat, 3 Oct 2020 14:11:54 -0400 Subject: [PATCH 17/19] chore: pass correct disabled args and reconsider cancel flow on set password --- .../account/password/change-password/index.hbs | 2 +- .../acceptance/accounts/set-password-test.js | 17 ++++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/ui/core/app/components/form/account/password/change-password/index.hbs b/ui/core/app/components/form/account/password/change-password/index.hbs index 156bc26d1..24548e843 100644 --- a/ui/core/app/components/form/account/password/change-password/index.hbs +++ b/ui/core/app/components/form/account/password/change-password/index.hbs @@ -44,7 +44,7 @@ diff --git a/ui/core/tests/acceptance/accounts/set-password-test.js b/ui/core/tests/acceptance/accounts/set-password-test.js index 545f4642c..d03dff3d1 100644 --- a/ui/core/tests/acceptance/accounts/set-password-test.js +++ b/ui/core/tests/acceptance/accounts/set-password-test.js @@ -71,13 +71,16 @@ module('Acceptance | accounts | set password', function (hooks) { await a11yAudit(); }); - test('can cancel setting new password', async function (assert) { - assert.expect(1); - await visit(urls.setPassword); - await fillIn('[name="password"]', 'update password'); - await click('form button:not([type="submit"])'); - assert.notOk(find('[name="password"]').textContent.trim()); - }); + // NOTE: since set password is a tabbed route with easy navigation away + // from the form, and the form does not directly operate on a model instance, + // a "cancel" flow doesn't make much sense. Disabling for now. + // test('can cancel setting new password', async function (assert) { + // assert.expect(1); + // await visit(urls.setPassword); + // await fillIn('[name="password"]', 'update password'); + // await click('form button:not([type="submit"])'); + // assert.notOk(find('[name="password"]').textContent.trim()); + // }); test('errors are displayed when setting password fails', async function (assert) { assert.expect(1); From 3fdba6e5f91d80ea3e243af2eb31e3c3e9bdaab8 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Sat, 3 Oct 2020 14:12:46 -0400 Subject: [PATCH 18/19] chore: remove unused method --- .../auth-method/accounts/account/set-password.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/ui/core/app/routes/scopes/scope/auth-methods/auth-method/accounts/account/set-password.js b/ui/core/app/routes/scopes/scope/auth-methods/auth-method/accounts/account/set-password.js index 03a582d1b..900c5eaba 100644 --- a/ui/core/app/routes/scopes/scope/auth-methods/auth-method/accounts/account/set-password.js +++ b/ui/core/app/routes/scopes/scope/auth-methods/auth-method/accounts/account/set-password.js @@ -47,14 +47,4 @@ export default class ScopesScopeAuthMethodsAuthMethodAccountsAccountSettingsRout this.notify.error(error.message, { closeAfter: null }); } } - - /** - * Redirect to hosts as if nothing ever happened. - */ - @action - cancel() { - this.replaceWith( - 'scopes.scope.auth-methods.auth-method.accounts.account.set-password' - ); - } } From c5b7d6a8e3dbc68647ef19450c608ec1f7f36fcc Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Sat, 3 Oct 2020 14:16:00 -0400 Subject: [PATCH 19/19] chore: remove unused cancel method from form --- .../form/account/password/set-password/index.hbs | 1 - .../form/account/password/set-password/index.js | 10 ---------- 2 files changed, 11 deletions(-) diff --git a/ui/core/app/components/form/account/password/set-password/index.hbs b/ui/core/app/components/form/account/password/set-password/index.hbs index df0ca8215..b77d6b659 100644 --- a/ui/core/app/components/form/account/password/set-password/index.hbs +++ b/ui/core/app/components/form/account/password/set-password/index.hbs @@ -1,6 +1,5 @@ this.resetPassword()); fn(password); } - - /** - * Unset password value before callback. - * @param {function} fn - */ - @action - cancel(fn) { - this.resetPassword(); - fn(); - } }