diff --git a/mon-pix/app/components/authentication/password-reset-demand/password-reset-demand-form.gjs b/mon-pix/app/components/authentication/password-reset-demand/password-reset-demand-form.gjs
index b9e7eeee5ab..9021f6c859f 100644
--- a/mon-pix/app/components/authentication/password-reset-demand/password-reset-demand-form.gjs
+++ b/mon-pix/app/components/authentication/password-reset-demand/password-reset-demand-form.gjs
@@ -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;
@@ -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;
}
diff --git a/mon-pix/app/services/request-manager-handlers/app-info-handler.js b/mon-pix/app/services/request-manager-handlers/app-info-handler.js
new file mode 100644
index 00000000000..6f60b703bf3
--- /dev/null
+++ b/mon-pix/app/services/request-manager-handlers/app-info-handler.js
@@ -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 }));
+ }
+}
diff --git a/mon-pix/app/services/request-manager-handlers/auth-handler.js b/mon-pix/app/services/request-manager-handlers/auth-handler.js
new file mode 100644
index 00000000000..226b1cbacf8
--- /dev/null
+++ b/mon-pix/app/services/request-manager-handlers/auth-handler.js
@@ -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 }));
+ }
+}
diff --git a/mon-pix/app/services/request-manager-handlers/json-handler.js b/mon-pix/app/services/request-manager-handlers/json-handler.js
new file mode 100644
index 00000000000..35636438429
--- /dev/null
+++ b/mon-pix/app/services/request-manager-handlers/json-handler.js
@@ -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 }));
+ }
+}
diff --git a/mon-pix/app/services/request-manager-handlers/locale-handler.js b/mon-pix/app/services/request-manager-handlers/locale-handler.js
new file mode 100644
index 00000000000..1123fca229a
--- /dev/null
+++ b/mon-pix/app/services/request-manager-handlers/locale-handler.js
@@ -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;
+ }
+}
diff --git a/mon-pix/app/services/request-manager.js b/mon-pix/app/services/request-manager.js
new file mode 100644
index 00000000000..828725d7a98
--- /dev/null
+++ b/mon-pix/app/services/request-manager.js
@@ -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]);
+ }
+}
diff --git a/mon-pix/tests/integration/components/authentication/password-reset-demand/password-reset-demand-form-test.gjs b/mon-pix/tests/integration/components/authentication/password-reset-demand/password-reset-demand-form-test.gjs
index aedba90906e..ad546fb2469 100644
--- a/mon-pix/tests/integration/components/authentication/password-reset-demand/password-reset-demand-form-test.gjs
+++ b/mon-pix/tests/integration/components/authentication/password-reset-demand/password-reset-demand-form-test.gjs
@@ -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();
@@ -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 = 'someone@example.net';
const locale = ENGLISH_INTERNATIONAL_LOCALE;
@@ -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 = 'someone@example.net';
const screen = await render();
@@ -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 = 'someone@example.net';
const screen = await render();
@@ -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',
- },
- });
-}
diff --git a/mon-pix/tests/unit/services/request-manager-test.js b/mon-pix/tests/unit/services/request-manager-test.js
new file mode 100644
index 00000000000..ba6523cd405
--- /dev/null
+++ b/mon-pix/tests/unit/services/request-manager-test.js
@@ -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 });
+}