Skip to content

Commit

Permalink
[BUGFIX] Utiliser la locale sur la demande de reset de password (PIX-…
Browse files Browse the repository at this point in the history
  • Loading branch information
pix-service-auto-merge authored Nov 8, 2024
2 parents 846de8b + 9b03bcb commit ac821b2
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 37 deletions.
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`,
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 });
}

0 comments on commit ac821b2

Please sign in to comment.