From 2f6c40312229303a04cb82d86334c992aef9a5e5 Mon Sep 17 00:00:00 2001 From: daproclaima Date: Tue, 10 Sep 2024 19:13:11 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=A5=85(frontend)=20catch=20new=20errors?= =?UTF-8?q?=20on=20mailbox=20creation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - catch errors related to MAIL_PROVISIONING_API_CREDENTIALS introduced in commit #ba30b1d3eec73718add6585f30c6b7959cb21850. Intentionally does not parse the error "Permission denied. Please check your MAIL_PROVISIONING_API_CREDENTIALS." as it means the user is neither admin or owner of the domain and should not access the mailbox creation form - update translations and component tests --- CHANGELOG.md | 1 + .../__tests__/ModalCreateMailbox.test.tsx | 220 +++++------------- .../apps/desk/src/i18n/translations.json | 2 +- 3 files changed, 64 insertions(+), 159 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0468db7c..5e6a9161c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to - 🥅(frontend) improve add & update group forms error handling #387 - ✨(frontend) allow group members filtering #363 - ✨(mailbox) send new mailbox confirmation email #397 +- 🥅(frontend) catch new errors on mailbox creation #392 ### Fixed diff --git a/src/frontend/apps/desk/src/features/mail-domains/components/__tests__/ModalCreateMailbox.test.tsx b/src/frontend/apps/desk/src/features/mail-domains/components/__tests__/ModalCreateMailbox.test.tsx index 699cf91c5..b7a08008a 100644 --- a/src/frontend/apps/desk/src/features/mail-domains/components/__tests__/ModalCreateMailbox.test.tsx +++ b/src/frontend/apps/desk/src/features/mail-domains/components/__tests__/ModalCreateMailbox.test.tsx @@ -1,23 +1,19 @@ -import { useMutation } from '@tanstack/react-query'; import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import fetchMock from 'fetch-mock'; -import React from 'react'; -import { APIError } from '@/api'; import { AppWrapper } from '@/tests/utils'; -import { CreateMailboxParams } from '../../api'; import { MailDomain } from '../../types'; import { ModalCreateMailbox } from '../ModalCreateMailbox'; const mockMailDomain: MailDomain = { - name: 'domain.fr', id: '456ac6ca-0402-4615-8005-69bc1efde43f', + name: 'example.com', + slug: 'example-com', + status: 'enabled', created_at: new Date().toISOString(), updated_at: new Date().toISOString(), - slug: 'domainfr', - status: 'enabled', abilities: { get: true, patch: true, @@ -28,23 +24,18 @@ const mockMailDomain: MailDomain = { }, }; -const mockOnSuccess = jest.fn(); -jest.mock('../../api/useCreateMailbox', () => { - const { createMailbox } = jest.requireActual('../../api/useCreateMailbox'); - - return { - useCreateMailbox: jest.fn().mockImplementation(({ onError }) => - useMutation({ - mutationFn: createMailbox, - onSuccess: mockOnSuccess, - onError: (error) => onError(error), - }), - ), - }; -}); +const toast = jest.fn(); +jest.mock('@openfun/cunningham-react', () => ({ + ...jest.requireActual('@openfun/cunningham-react'), + useToastProvider: () => ({ + toast, + }), +})); describe('ModalCreateMailbox', () => { const mockCloseModal = jest.fn(); + const apiUrl = `end:/mail-domains/${mockMailDomain.slug}/mailboxes/`; + const renderModalCreateMailbox = () => { return render( { beforeEach(() => { jest.clearAllMocks(); - }); - - afterEach(() => { fetchMock.restore(); }); - it('renders all the elements', () => { + it('renders the modal with all fields and buttons', () => { renderModalCreateMailbox(); + const { formTag, inputFirstName, @@ -98,23 +87,7 @@ describe('ModalCreateMailbox', () => { expect(buttonSubmit).toBeVisible(); }); - it('clicking on cancel button closes modal', async () => { - const user = userEvent.setup(); - - renderModalCreateMailbox(); - - const { buttonCancel } = getFormElements(); - - expect(buttonCancel).toBeVisible(); - - await user.click(buttonCancel); - - expect(mockCloseModal).toHaveBeenCalled(); - }); - - it('displays validation errors on empty submit', async () => { - const user = userEvent.setup(); - + it('shows validation errors for empty fields', async () => { renderModalCreateMailbox(); const { @@ -126,17 +99,16 @@ describe('ModalCreateMailbox', () => { } = getFormElements(); // To bypass html form validation we need to fill and clear the fields - await user.type(inputFirstName, 'John'); - await user.type(inputLastName, 'Doe'); - await user.type(inputLocalPart, 'john.doe'); - await user.type(inputEmailAddress, 'john.doe@mail.com'); - - await user.clear(inputFirstName); - await user.clear(inputLastName); - await user.clear(inputLocalPart); - await user.clear(inputEmailAddress); + await userEvent.type(inputFirstName, 'John'); + await userEvent.type(inputLastName, 'Doe'); + await userEvent.type(inputLocalPart, 'john.doe'); + await userEvent.type(inputEmailAddress, 'john.doe@mail.com'); + await userEvent.clear(inputFirstName); + await userEvent.clear(inputLastName); + await userEvent.clear(inputLocalPart); + await userEvent.clear(inputEmailAddress); - await user.click(buttonSubmit); + await userEvent.click(buttonSubmit); expect(screen.getByText(`@${mockMailDomain.name}`)).toBeVisible(); @@ -161,10 +133,10 @@ describe('ModalCreateMailbox', () => { expect(buttonSubmit).toBeDisabled(); }); - it('submits the form when validation passes', async () => { - fetchMock.mock(`end:mail-domains/${mockMailDomain.slug}/mailboxes/`, 201); - - const user = userEvent.setup(); + it('calls the createMailbox API on form submission with valid data', async () => { + fetchMock.postOnce(apiUrl, { + status: 201, + }); renderModalCreateMailbox(); @@ -176,98 +148,34 @@ describe('ModalCreateMailbox', () => { buttonSubmit, } = getFormElements(); - await user.type(inputFirstName, 'John'); - await user.type(inputLastName, 'Doe'); - await user.type(inputLocalPart, 'john.doe'); - await user.type(inputEmailAddress, 'john.doe@mail.com'); + await userEvent.type(inputFirstName, 'John'); + await userEvent.type(inputLastName, 'Doe'); + await userEvent.type(inputLocalPart, 'johndoe'); + await userEvent.type(inputEmailAddress, 'john.doe@mail.com'); - await user.click(buttonSubmit); - - await waitFor(() => { - expect( - screen.queryByText(/Please enter your first name/i), - ).not.toBeInTheDocument(); - }); - await waitFor(() => { - expect( - screen.queryByText(/Please enter your last name/i), - ).not.toBeInTheDocument(); - }); + await userEvent.click(buttonSubmit); await waitFor(() => { - expect( - screen.queryByText(/You must have minimum 1 character/i), - ).not.toBeInTheDocument(); - }); - - expect(fetchMock.lastOptions()).toEqual({ - body: JSON.stringify({ - first_name: 'John', - last_name: 'Doe', - local_part: 'john.doe', - secondary_email: 'john.doe@mail.com', - }), - credentials: 'include', - headers: { 'Content-Type': 'application/json' }, - method: 'POST', + expect(fetchMock.called(apiUrl)).toBeTruthy(); }); - - expect(mockOnSuccess).toHaveBeenCalled(); - }); - - it('submits the form on key enter press', async () => { - fetchMock.mock(`end:mail-domains/${mockMailDomain.slug}/mailboxes/`, 201); - const user = userEvent.setup(); - - renderModalCreateMailbox(); - - const { - inputFirstName, - inputLastName, - inputLocalPart, - inputEmailAddress, - buttonSubmit, - } = getFormElements(); - - await user.type(inputFirstName, 'John'); - await user.type(inputLastName, 'Doe'); - await user.type(inputLocalPart, 'john.doe'); - - await user.type(inputEmailAddress, 'john.doe@mail.com'); - - await user.type(buttonSubmit, '{enter}'); - - expect(fetchMock.lastOptions()).toEqual({ - body: JSON.stringify({ + expect(fetchMock.lastCall(apiUrl)?.[1]?.body).toEqual( + JSON.stringify({ first_name: 'John', last_name: 'Doe', - local_part: 'john.doe', + local_part: 'johndoe', secondary_email: 'john.doe@mail.com', }), - credentials: 'include', - headers: { 'Content-Type': 'application/json' }, - method: 'POST', - }); - - expect(mockOnSuccess).toHaveBeenCalled(); + ); }); - it('displays right error message error when mailbox prefix is already used', async () => { - // mockCreateMailbox.mockRejectedValueOnce( - // new APIError('Failed to create the mailbox', { - // status: 400, - // cause: ['Mailbox with this Local_part and Domain already exists.'], - // }), - // ); - fetchMock.mock(`end:mail-domains/${mockMailDomain.slug}/mailboxes/`, { + it('shows error message when mailbox prefix is already used', async () => { + fetchMock.postOnce(apiUrl, { status: 400, body: { local_part: 'Mailbox with this Local_part and Domain already exists.', }, }); - const user = userEvent.setup(); - renderModalCreateMailbox(); const { @@ -278,28 +186,32 @@ describe('ModalCreateMailbox', () => { buttonSubmit, } = getFormElements(); - await user.type(inputFirstName, 'John'); - await user.type(inputLastName, 'Doe'); - await user.type(inputLocalPart, 'john.doe'); - await user.type(inputEmailAddress, 'john.doe@mail.com'); + await userEvent.type(inputFirstName, 'John'); + await userEvent.type(inputLastName, 'Doe'); + await userEvent.type(inputLocalPart, 'johndoe'); + await userEvent.type(inputEmailAddress, 'john.doe@mail.com'); - await user.click(buttonSubmit); + await userEvent.click(buttonSubmit); await waitFor(() => { expect( screen.getByText(/This email prefix is already used./i), ).toBeInTheDocument(); }); - - expect(inputLocalPart).toHaveFocus(); }); - it('displays right error message error when error 500 is received', async () => { - fetchMock.mock(`end:mail-domains/${mockMailDomain.slug}/mailboxes/`, { - status: 500, - }); + it('closes the modal when cancel button is clicked', async () => { + renderModalCreateMailbox(); - const user = userEvent.setup(); + const { buttonCancel } = getFormElements(); + + await userEvent.click(buttonCancel); + + expect(mockCloseModal).toHaveBeenCalled(); + }); + + it('disables the submit button while the form is submitting', async () => { + fetchMock.postOnce(apiUrl, new Promise(() => {})); // Simulate pending state renderModalCreateMailbox(); @@ -311,23 +223,15 @@ describe('ModalCreateMailbox', () => { buttonSubmit, } = getFormElements(); - await user.type(inputFirstName, 'John'); - await user.type(inputLastName, 'Doe'); - await user.type(inputLocalPart, 'john.doe'); - await user.type(inputEmailAddress, 'john.doe@mail.com'); + await userEvent.type(inputFirstName, 'John'); + await userEvent.type(inputLastName, 'Doe'); + await userEvent.type(inputLocalPart, 'johndoe'); + await userEvent.type(inputEmailAddress, 'john.doe@mail.com'); - await user.click(buttonSubmit); + await userEvent.click(buttonSubmit); await waitFor(() => { - expect( - screen.getByText( - 'Your request cannot be processed because the server is experiencing an error. If the problem ' + - 'persists, please contact our support to resolve the issue: suiteterritoriale@anct.gouv.fr', - ), - ).toBeInTheDocument(); + expect(buttonSubmit).toBeDisabled(); }); - - expect(inputFirstName).toHaveFocus(); - expect(buttonSubmit).toBeEnabled(); }); }); diff --git a/src/frontend/apps/desk/src/i18n/translations.json b/src/frontend/apps/desk/src/i18n/translations.json index 4d82e40df..4a7173f31 100644 --- a/src/frontend/apps/desk/src/i18n/translations.json +++ b/src/frontend/apps/desk/src/i18n/translations.json @@ -138,7 +138,6 @@ "Teams": "Équipes", "The National Agency for Territorial Cohesion undertakes to make its\n service accessible, in accordance with article 47 of law no. 2005-102\n of February 11, 2005.": "L'Agence Nationale de la Cohésion des Territoires s’engage à rendre son service accessible, conformément à l’article 47 de la loi n° 2005-102 du 11 février 2005.", "The domain name encounters an error. Please contact our support team to solve the problem:": "Le nom de domaine rencontre une erreur. Veuillez contacter notre support pour résoudre le problème :", - "The mail domain secret is misconfigured. Please, contact our support team to solve the issue: suiteterritoriale@anct.gouv.fr": "Le secret du domaine de messagerie est mal configuré. Veuillez contacter notre support pour résoudre le problème : suiteterritoriale@anct.gouv.fr", "The member has been removed from the team": "Le membre a été supprimé de votre groupe", "The role has been updated": "Le rôle a bien été mis à jour", "The team has been removed.": "Le groupe a été supprimé.", @@ -171,6 +170,7 @@ "You must have minimum 1 character": "Vous devez entrer au moins 1 caractère", "Your domain name is being validated. You will not be able to create mailboxes until your domain name has been validated by our team.": "Votre nom de domaine est en cours de validation. Vous ne pourrez créer de boîtes mail que lorsque votre nom de domaine sera validé par notre équipe.", "Your request cannot be processed because the server is experiencing an error. If the problem persists, please contact our support to resolve the issue: suiteterritoriale@anct.gouv.fr": "Votre demande ne peut pas être traitée car le serveur rencontre une erreur. Si le problème persiste, veuillez contacter notre support pour résoudre le problème : suiteterritoriale@anct.gouv.fr", + "Your request to create a mailbox cannot be completed due to incorrect settings on our server. Please contact our support team to resolve the problem: suiteterritoriale@anct.gouv.fr": "Votre demande de création de boîte mail ne peut pas être complétée en raison de paramètres incorrects sur notre serveur. Veuillez contacter notre équipe support pour résoudre le problème : suiteterritoriale@anct.gouv.fr", "[disabled]": "[désactivé]", "[enabled]": "[actif]", "[failed]": "[erroné]",