diff --git a/wherehows-web/app/components/dataset-deprecation.ts b/wherehows-web/app/components/dataset-deprecation.ts index d705840fa3..f98be57573 100644 --- a/wherehows-web/app/components/dataset-deprecation.ts +++ b/wherehows-web/app/components/dataset-deprecation.ts @@ -1,6 +1,6 @@ import Component from '@ember/component'; import { inject } from '@ember/service'; -import { getProperties, computed } from '@ember/object'; +import { getProperties, computed, set } from '@ember/object'; import ComputedProperty, { oneWay } from '@ember/object/computed'; import { baseCommentEditorOptions } from 'wherehows-web/constants'; import Notifications, { NotificationEvent } from 'wherehows-web/services/notifications'; @@ -102,8 +102,12 @@ export default class DatasetDeprecation extends Component { const { onUpdateDeprecation } = this; if (onUpdateDeprecation) { + const noteValue = deprecatedAlias ? deprecationNoteAlias : ''; + try { - await onUpdateDeprecation(deprecatedAlias, deprecationNoteAlias); + await onUpdateDeprecation(deprecatedAlias, noteValue); + set(this, 'deprecationNoteAlias', noteValue); + notify(NotificationEvent.success, { content: 'Successfully updated deprecation status' }); diff --git a/wherehows-web/app/components/login-form.js b/wherehows-web/app/components/login-form.js deleted file mode 100644 index cf3923846d..0000000000 --- a/wherehows-web/app/components/login-form.js +++ /dev/null @@ -1,18 +0,0 @@ -import Ember from 'ember'; - -const { - Component -} = Ember; - -export default Component.extend({ - classNames: ['nacho-login-form'], - actions: { - /** - * Handle the login for submission - */ - userDidSubmit() { - // Trigger action on parent controller - this.get('onSubmit')(); - } - } -}) \ No newline at end of file diff --git a/wherehows-web/app/components/login-form.ts b/wherehows-web/app/components/login-form.ts new file mode 100644 index 0000000000..8671f59b53 --- /dev/null +++ b/wherehows-web/app/components/login-form.ts @@ -0,0 +1,35 @@ +import Component from '@ember/component'; +import { get } from '@ember/object'; +import { assert } from '@ember/debug'; + +export default class LoginForm extends Component { + classNames = ['nacho-login-form']; + + /** + * External action to be invoked on form submission + * @type {Function} + * @memberof LoginForm + */ + onSubmit: Function; + + constructor() { + super(...arguments); + + // Ensure that the onSubmit action passed in on instantiation is a callable action + const typeOfOnSubmit = typeof this.onSubmit; + assert( + `Expected action onSubmit to be an function (Ember action), got ${typeOfOnSubmit}`, + typeOfOnSubmit === 'function' + ); + } + + actions = { + /** + * Handle the login for submission + */ + userDidSubmit(this: LoginForm) { + // Trigger action on parent controller + get(this, 'onSubmit')(); + } + }; +} diff --git a/wherehows-web/app/controllers/login.js b/wherehows-web/app/controllers/login.js index fc58e4e6e4..a4e60f269d 100644 --- a/wherehows-web/app/controllers/login.js +++ b/wherehows-web/app/controllers/login.js @@ -1,12 +1,6 @@ import Ember from 'ember'; -const { - Controller, - computed, - get, - setProperties, - inject: { service } -} = Ember; +const { Controller, computed, get, setProperties, inject: { service } } = Ember; export default Controller.extend({ session: service(), @@ -22,15 +16,11 @@ export default Controller.extend({ * Using the session service, authenticate using the custom ldap authenticator */ authenticateUser() { - const { username, password } = this.getProperties([ - 'username', - 'password' - ]); + const { username, password } = this.getProperties(['username', 'password']); get(this, 'session') .authenticate('authenticator:custom-ldap', username, password) - .catch(({ responseText = 'Bad Credentials' }) => - setProperties(this, { errorMessage: responseText })); + .catch(({ responseText = 'Bad Credentials' }) => setProperties(this, { errorMessage: responseText })); } } }); diff --git a/wherehows-web/mirage/config.ts b/wherehows-web/mirage/config.ts index ba977af6f2..c271e0055d 100644 --- a/wherehows-web/mirage/config.ts +++ b/wherehows-web/mirage/config.ts @@ -1,32 +1,20 @@ import { faker } from 'ember-cli-mirage'; import { IFunctionRouteHandler, IMirageServer } from 'wherehows-web/typings/ember-cli-mirage'; import { ApiStatus } from 'wherehows-web/utils/api/shared'; -import { getConfig } from "./helpers/config"; +import { getConfig } from 'wherehows-web/mirage/helpers/config'; +import { getAuth } from 'wherehows-web/mirage/helpers/authenticate'; export default function(this: IMirageServer) { - this.get('/config', getConfig); - this.post('/authenticate', function({}, request: any) { - const username = JSON.parse(request.requestBody).username; - const password = JSON.parse(request.requestBody).password; - - if (password === null || password === undefined) { - return 'Missing or invalid [credentials]'; - } else if (password === 'invalidPassword') { - return 'Invalid Password'; - } - return { - status: ApiStatus.OK, - data: {username: username, uuid: faker.random.uuid()} - }; - }); + this.post('/authenticate', getAuth); this.namespace = '/api/v1'; interface IComplianceSuggestionsObject { - complianceSuggestions: any; + complianceSuggestions: any; } + this.get('/datasets/:id/compliance/suggestions', function( this: IFunctionRouteHandler, { complianceSuggestions }: IComplianceSuggestionsObject @@ -44,9 +32,8 @@ export default function(this: IMirageServer) { interface IFlowsObject { flows: any; } - this.get('/flows', function( - this: IFunctionRouteHandler, - { flows }: IFlowsObject, request: any) { + + this.get('/flows', function(this: IFunctionRouteHandler, { flows }: IFlowsObject, request: any) { const { page } = request.queryParams; const flowsArr = this.serialize(flows.all()); const count = faker.random.number({ min: 20000, max: 40000 }); @@ -59,7 +46,7 @@ export default function(this: IMirageServer) { flows: flowsArr, itemsPerPage: itemsPerPage, page: page, - totalPages: Math.round(count / itemsPerPage), + totalPages: Math.round(count / itemsPerPage) } }; }); @@ -81,7 +68,10 @@ export default function(this: IMirageServer) { } this.get('/datasets', function( this: IFunctionRouteHandler, - { datasets }: IDatasetsObject, { owners }: IOwnersObject, request: any) { + { datasets }: IDatasetsObject, + { owners }: IOwnersObject, + request: any + ) { const { page } = request.queryParams; const datasetsArr = this.serialize(datasets.all()); const ownersArr = this.serialize(owners.all()); @@ -99,7 +89,7 @@ export default function(this: IMirageServer) { page: page, itemsPerPage: itemsPerPage, totalPages: Math.round(count / itemsPerPage), - datasets: newDatasetsArr, + datasets: newDatasetsArr } }; }); @@ -141,8 +131,8 @@ export default function(this: IMirageServer) { email: testUser + '@linkedin.com', name: testUser, userSetting: { - "detailDefaultView": null, - "defaultWatch":null + detailDefaultView: null, + defaultWatch: null } }, status: ApiStatus.OK @@ -151,6 +141,4 @@ export default function(this: IMirageServer) { this.passthrough(); } -export function testConfig(this: IMirageServer) { - this.get('/config', getConfig); -} +export function testConfig(this: IMirageServer) {} diff --git a/wherehows-web/mirage/helpers/authenticate.ts b/wherehows-web/mirage/helpers/authenticate.ts new file mode 100644 index 0000000000..9fe76edd2d --- /dev/null +++ b/wherehows-web/mirage/helpers/authenticate.ts @@ -0,0 +1,33 @@ +import { ApiStatus } from 'wherehows-web/utils/api/shared'; +import { Response, faker } from 'ember-cli-mirage'; + +type StringOrNullOrUndefined = string | null | void; + +const textContentHeader = { 'Content-Type': 'text/plain; charset=utf-8' }; + +/** + * Returns a config object for the config endpoint + * @param {object} _schema the auth table / factory object + * @param {requestBody} property on the request object passed in to mirage function handlers + * @return {{status: ApiStatus, data: object}} + */ +const getAuth = (_schema: {}, { requestBody }: { requestBody: string }) => { + const { username, password } = <{ username: StringOrNullOrUndefined; password: StringOrNullOrUndefined }>JSON.parse( + requestBody + ); + + if (!password) { + return new Response(400, textContentHeader, 'Missing or invalid [credentials]'); + } + + if (password === 'invalidPassword') { + return new Response(401, textContentHeader, 'Invalid Password'); + } + + return { + status: ApiStatus.OK, + data: { username, uuid: faker.random.uuid() } + }; +}; + +export { getAuth }; diff --git a/wherehows-web/mirage/models/authenticate.js b/wherehows-web/mirage/models/authenticate.js new file mode 100644 index 0000000000..770b50936d --- /dev/null +++ b/wherehows-web/mirage/models/authenticate.js @@ -0,0 +1,3 @@ +import { Model } from 'ember-cli-mirage'; + +export default Model.extend({}); diff --git a/wherehows-web/mirage/scenarios/default.js b/wherehows-web/mirage/scenarios/default.js index 65e22ad809..96f3d93fd7 100644 --- a/wherehows-web/mirage/scenarios/default.js +++ b/wherehows-web/mirage/scenarios/default.js @@ -1,14 +1,8 @@ export default function(server) { - - const fixtures = [ - 'dataset-nodes', - 'metric-metrics', - 'user-entities' - ]; + const fixtures = ['dataset-nodes', 'metric-metrics', 'user-entities']; server.loadFixtures(...fixtures); server.createList('complianceSuggestion', 5); server.createList('owner', 6); server.createList('dataset', 10); server.createList('flow', 10); - } diff --git a/wherehows-web/package.json b/wherehows-web/package.json index 12249c8c49..e4b73daea2 100644 --- a/wherehows-web/package.json +++ b/wherehows-web/package.json @@ -50,12 +50,12 @@ "ember-concurrency": "^0.8.10", "ember-export-application-global": "^2.0.0", "ember-fetch": "^3.4.3", + "ember-load-initializers": "^1.0.0", "ember-lodash-shim": "^2.0.5", "ember-metrics": "^0.12.1", "ember-pikaday": "^2.2.1", "ember-redux-shim": "^1.1.1", "ember-redux-thunk-shim": "^1.1.2", - "ember-load-initializers": "^1.0.0", "ember-resolver": "^4.5.0", "ember-source": "~2.16.0", "ember-symbol-observable": "^0.1.2", @@ -67,12 +67,12 @@ "eyeglass-restyle": "^1.1.0", "husky": "^0.14.3", "lint-staged": "^4.3.0", + "loader.js": "^4.6.0", "node-sass": "^4.5.3", "prettier": "^1.7.4", "redux": "^3.6.0", "redux-thunk": "^2.2.0", - "typescript": "^2.5.3", - "loader.js": "^4.6.0" + "typescript": "^2.5.3" }, "dependencies": { "dynamic-link": "^0.2.3", diff --git a/wherehows-web/tests/acceptance/login-test.js b/wherehows-web/tests/acceptance/login-test.js index e0830c6225..e18d372b5b 100644 --- a/wherehows-web/tests/acceptance/login-test.js +++ b/wherehows-web/tests/acceptance/login-test.js @@ -1,11 +1,11 @@ import { test } from 'qunit'; import moduleForAcceptance from 'wherehows-web/tests/helpers/module-for-acceptance'; +import wait from 'ember-test-helpers/wait'; import { loginContainer, authenticationUrl, invalidCredentials, testUser, - testPassword, testPasswordInvalid } from 'wherehows-web/tests/helpers/login/constants'; import { @@ -40,7 +40,9 @@ test('should render login form', function(assert) { test('should display error message with empty credentials', async function(assert) { assert.expect(2); await fillIn(loginUserInput, testUser); - await click('button[type=submit]'); + await click(loginSubmitButton); + + await wait(); assert.ok(find('#login-error').text().length, 'error message element is rendered'); @@ -48,31 +50,26 @@ test('should display error message with empty credentials', async function(asser find('#login-error') .text() .trim(), - invalidCredentials + invalidCredentials, + 'displays missing or invalid credentials message' ); }); -test('Login with an empty password', async function(assert) { - await fillIn(loginUserInput, testUser); - await click(loginSubmitButton); - - assert.equal( - find('#login-error') - .text() - .trim(), - invalidCredentials - ); -}); - -test('Login with an invalid password', async function(assert) { +test('should display invalid password message with invalid password entered', async function(assert) { + assert.expect(2); await fillIn(loginUserInput, testUser); await fillIn(loginPasswordInput, testPasswordInvalid); await click(loginSubmitButton); + await wait(); + + assert.ok(find('#login-error').text().length, 'error message element is rendered'); + assert.equal( find('#login-error') .text() .trim(), - 'Invalid Password' + 'Invalid Password', + 'displays invalid password message in error message container' ); }); diff --git a/wherehows-web/tests/integration/components/login-form-test.js b/wherehows-web/tests/integration/components/login-form-test.js new file mode 100644 index 0000000000..82e5999829 --- /dev/null +++ b/wherehows-web/tests/integration/components/login-form-test.js @@ -0,0 +1,32 @@ +import { moduleForComponent, test } from 'ember-qunit'; +import hbs from 'htmlbars-inline-precompile'; +import { run } from '@ember/runloop'; + +moduleForComponent('login-form', 'Integration | Component | login form', { + integration: true +}); + +test('it renders', function(assert) { + this.set('authenticateUser', () => {}); + this.render(hbs`{{login-form onSubmit=(action authenticateUser)}}`); + + assert.equal(this.$('#login-username').length, 1, 'has an input for username'); + assert.equal(this.$('#login-password').length, 1, 'has an input for password'); + assert.equal(this.$('[type=submit]').length, 1, 'has a button for submission'); +}); + +test('triggers the onSubmit action when clicked', function(assert) { + assert.expect(2); + let submitActionCallCount = false; + + this.set('authenticateUser', function() { + submitActionCallCount = true; + }); + + this.render(hbs`{{login-form onSubmit=(action authenticateUser)}}`); + + assert.equal(submitActionCallCount, false, 'submit action is not called on render'); + run(() => document.querySelector('.nacho-login-form [type=submit]').click()); + + assert.equal(submitActionCallCount, true, 'submit action is called once'); +}); diff --git a/wherehows-web/tsconfig.json b/wherehows-web/tsconfig.json index 1514314d96..1eb0b5ec25 100644 --- a/wherehows-web/tsconfig.json +++ b/wherehows-web/tsconfig.json @@ -20,7 +20,8 @@ "downlevelIteration": true, "paths": { "wherehows-web/*": ["app/*"], - "wherehows-web/tests/*": ["tests/*"] + "wherehows-web/tests/*": ["tests/*"], + "wherehows-web/mirage/*": ["mirage/*"] } }, "include": ["app/**/*", "tests/**/*", "mirage/**/*"],