Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUGFIX] Utiliser la locale sur la demande de reset de password (PIX-15065) #10497

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import PasswordResetDemandReceivedInfo from './password-reset-demand-received-in

export default class PasswordResetDemandForm extends Component {
@service errors;
@service requestManager;

@tracked globalError = this.errors.hasErrors && this.errors.shift();
@tracked isLoading = false;
Expand Down Expand Up @@ -47,22 +48,19 @@ export default class PasswordResetDemandForm extends Component {
this.isPasswordResetDemandReceived = false;

try {
const response = await window.fetch(`${ENV.APP.API_HOST}/api/password-reset-demands`, {
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
await this.requestManager.request({
url: `${ENV.APP.API_HOST}/api/password-reset-demands`,
bpetetot marked this conversation as resolved.
Show resolved Hide resolved
method: 'POST',
body: JSON.stringify({ email }),
});

if (!response.ok && response.status != 404) {
throw new Error(`Response status: ${response.status}`);
}

this.isPasswordResetDemandReceived = true;
} catch (error) {
this.globalError = 'common.api-error-messages.internal-server-error';
if (error.status === 404) {
this.isPasswordResetDemandReceived = true;
} else {
this.globalError = 'common.api-error-messages.internal-server-error';
}
} finally {
this.isLoading = false;
}
Expand Down
15 changes: 15 additions & 0 deletions mon-pix/app/services/request-manager-handlers/app-info-handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import ENV from 'mon-pix/config/environment';

/**
* Request manager handler adding application info in request headers.
* See: https://github.com/emberjs/data/blob/main/guides/requests/examples/1-auth.md
*/
export default class AppInfoHandler {
request(context, next) {
const headers = new Headers(context.request.headers);

headers.append('X-App-Version', ENV.APP.APP_VERSION);

return next(Object.assign({}, context.request, { headers }));
}
}
20 changes: 20 additions & 0 deletions mon-pix/app/services/request-manager-handlers/auth-handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { inject as service } from '@ember/service';

/**
* Request manager handler adding authentication credentials in the request.
* See: https://github.com/emberjs/data/blob/main/guides/requests/examples/1-auth.md
*/
export default class AuthHandler {
@service session;

request(context, next) {
const headers = new Headers(context.request.headers);

const { isAuthenticated, data } = this.session;
if (isAuthenticated) {
headers.append('Authorization', `Bearer ${data.authenticated.access_token}`);
}

return next(Object.assign({}, context.request, { headers }));
}
}
14 changes: 14 additions & 0 deletions mon-pix/app/services/request-manager-handlers/json-handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Request manager handler to manage JSON request.
* See: https://github.com/emberjs/data/blob/main/guides/requests/examples/1-auth.md
*/
export default class JsonHandler {
request(context, next) {
const headers = new Headers(context.request.headers);

headers.append('Accept', 'application/json');
headers.append('Content-Type', 'application/json');

return next(Object.assign({}, context.request, { headers }));
}
}
25 changes: 25 additions & 0 deletions mon-pix/app/services/request-manager-handlers/locale-handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { inject as service } from '@ember/service';

const FRENCH_FRANCE_LOCALE = 'fr-fr';

/**
* Request manager handler adding user locale in request header.
* See: https://github.com/emberjs/data/blob/main/guides/requests/examples/1-auth.md
*/
export default class LocaleHandler {
@service currentDomain;
@service intl;

request(context, next) {
const headers = new Headers(context.request.headers);

headers.append('Accept-Language', this._locale);

return next(Object.assign({}, context.request, { headers }));
}

get _locale() {
if (this.currentDomain.isFranceDomain) return FRENCH_FRANCE_LOCALE;
return this.intl.primaryLocale;
}
}
32 changes: 32 additions & 0 deletions mon-pix/app/services/request-manager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { getOwner, setOwner } from '@ember/application';
import RequestManager from '@ember-data/request';
import Fetch from '@ember-data/request/fetch';

import AppInfoHandler from './request-manager-handlers/app-info-handler.js';
import AuthHandler from './request-manager-handlers/auth-handler.js';
import JsonHandler from './request-manager-handlers/json-handler.js';
import LocaleHandler from './request-manager-handlers/locale-handler.js';

/**
* Request manager preconfigured for authenticated or not HTTP requests.
* see: https://api.emberjs.com/ember-data/release/modules/@ember-data%2Frequest
*/
export default class RequestManagerService extends RequestManager {
constructor(createArgs) {
super(createArgs);

const authHandler = new AuthHandler();
setOwner(authHandler, getOwner(this));

const localHandler = new LocaleHandler();
setOwner(localHandler, getOwner(this));

const appInfoHandler = new AppInfoHandler();
setOwner(appInfoHandler, getOwner(this));

const jsonHandler = new JsonHandler();
setOwner(jsonHandler, getOwner(this));

this.use([authHandler, localHandler, appInfoHandler, jsonHandler, Fetch]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ const I18N_KEYS = {
module('Integration | Component | Authentication | PasswordResetDemand | password-reset-demand-form', function (hooks) {
setupIntlRenderingTest(hooks);

let requestManagerService;

hooks.beforeEach(function () {
requestManagerService = this.owner.lookup('service:requestManager');
sinon.stub(requestManagerService, 'request');
});

test('it displays all elements of component successfully', async function (assert) {
// given
const screen = await render(<template><PasswordResetDemandForm /></template>);
Expand Down Expand Up @@ -78,11 +85,7 @@ module('Integration | Component | Authentication | PasswordResetDemand | passwor
module('when the password-reset-demand is successful', function () {
test('it displays a "password reset demand received" info (without any error message)', async function (assert) {
// given
window.fetch.resolves(
fetchMock({
status: 201,
}),
);
requestManagerService.request.resolves({ response: { ok: true, status: 201 } });

const email = '[email protected]';
const locale = ENGLISH_INTERNATIONAL_LOCALE;
Expand Down Expand Up @@ -127,14 +130,7 @@ module('Integration | Component | Authentication | PasswordResetDemand | passwor
module('when there is no corresponding user account', function () {
test('it displays a "password reset demand received" info (without any error message to avoid email enumeration)', async function (assert) {
// given
window.fetch.resolves(
fetchMock({
status: 404,
body: {
errors: [{ title: 'Not Found' }],
},
}),
);
requestManagerService.request.rejects({ status: 404 });

const email = '[email protected]';
const screen = await render(<template><PasswordResetDemandForm /></template>);
Expand All @@ -157,11 +153,7 @@ module('Integration | Component | Authentication | PasswordResetDemand | passwor
module('when there is an unknown error', function () {
test('it displays an "unknown error" error message', async function (assert) {
// given
window.fetch.resolves(
fetchMock({
status: 500,
}),
);
requestManagerService.request.rejects({ status: 500 });

const email = '[email protected]';
const screen = await render(<template><PasswordResetDemandForm /></template>);
Expand Down Expand Up @@ -195,12 +187,3 @@ module('Integration | Component | Authentication | PasswordResetDemand | passwor
});
});
});

function fetchMock({ body, status }) {
return new window.Response(JSON.stringify(body), {
status,
headers: {
'Content-type': 'application/json',
},
});
}
101 changes: 101 additions & 0 deletions mon-pix/tests/unit/services/request-manager-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { setupTest } from 'ember-qunit';
import { module, test } from 'qunit';
import sinon from 'sinon';

module('Unit | Service | request-manager', function (hooks) {
setupTest(hooks);
let requestManagerService;
let sessionService;
let currentDomainService;
let intlService;

hooks.beforeEach(function () {
sinon.stub(window, 'fetch');

requestManagerService = this.owner.lookup('service:requestManager');

sessionService = this.owner.lookup('service:session');
sinon.stub(sessionService, 'isAuthenticated').value(false);
sinon.stub(sessionService, 'data').value(null);

currentDomainService = this.owner.lookup('service:currentDomain');
sinon.stub(currentDomainService, 'isFranceDomain').value(false);

intlService = this.owner.lookup('service:intl');
sinon.stub(intlService, 'primaryLocale').value('fr');
});

hooks.afterEach(function () {
sinon.restore();
});

module('request()', function () {
test('it requests successfully with default headers', async function (assert) {
// given
window.fetch.resolves(responseMock({ status: 200, data: { foo: 'bar' } }));

// when
const result = await requestManagerService.request({ url: '/test', method: 'GET' });

// then
assert.strictEqual(result.response.status, 200);
assert.deepEqual(result.content, { foo: 'bar' });

const [url, { headers }] = window.fetch.getCall(0).args;
assert.strictEqual(url, '/test');
assert.strictEqual(headers.get('Accept-Language'), 'fr');
assert.strictEqual(headers.get('X-App-Version'), 'development');
assert.strictEqual(headers.get('Accept'), 'application/json');
assert.strictEqual(headers.get('Content-Type'), 'application/json');
});

module('when user is authenticated', function () {
test('it sets the Authorization header with the access token', async function (assert) {
// given
window.fetch.resolves(responseMock({ status: 200, data: { foo: 'bar' } }));
sinon.stub(sessionService, 'isAuthenticated').value(true);
sinon.stub(sessionService, 'data').value({ authenticated: { access_token: 'baz' } });

// when
await requestManagerService.request({ url: '/test', method: 'GET' });

// then
const [url, { headers }] = window.fetch.getCall(0).args;
assert.strictEqual(url, '/test');
assert.strictEqual(headers.get('Authorization'), 'Bearer baz');
});
});

module('when it is on France domain', function () {
test('it sets the header Accept-Language to fr-fr', async function (assert) {
// given
window.fetch.resolves(responseMock({ status: 200, data: { foo: 'bar' } }));
sinon.stub(currentDomainService, 'isFranceDomain').value(true);

// when
await requestManagerService.request({ url: '/test', method: 'GET' });

// then
const [url, { headers }] = window.fetch.getCall(0).args;
assert.strictEqual(url, '/test');
assert.strictEqual(headers.get('Accept-Language'), 'fr-fr');
});
});

module('when an error occured on HTTP call', function () {
test('it throws an exception with error details', async function (assert) {
// given
window.fetch.resolves(responseMock({ status: 400, data: { error: 'KO' } }));

// when
assert.rejects(requestManagerService.request({ url: '/test', method: 'GET' }), function (err) {
return err.status === 400 && err.content.error === 'KO';
});
});
});
});
});

function responseMock({ status, data }) {
return new window.Response(JSON.stringify(data), { status });
}
Loading