diff --git a/back/src/users/resolvers/mutations/__tests__/editProfile.integration.ts b/back/src/users/resolvers/mutations/__tests__/editProfile.integration.ts index 3e7eeaa78a..d6e1bf0373 100644 --- a/back/src/users/resolvers/mutations/__tests__/editProfile.integration.ts +++ b/back/src/users/resolvers/mutations/__tests__/editProfile.integration.ts @@ -26,6 +26,43 @@ export const UPDATE_TRACKING_CONSENT = gql` `; describe("mutation editProfile", () => { + it("should not allow name with less than 2 letters", async () => { + const user = await userFactory(); + const { mutate } = makeClient({ ...user, auth: AuthType.Session }); + const newName = "A"; + const { errors } = await mutate(EDIT_PROFILE, { + variables: { name: newName } + }); + const updatedUser = await prisma.user.findUniqueOrThrow({ + where: { id: user.id } + }); + expect(errors).toEqual([ + expect.objectContaining({ + message: `Le nom doit contenir au moins 2 lettres.`, + extensions: expect.objectContaining({ code: ErrorCode.BAD_USER_INPUT }) + }) + ]); + expect(updatedUser.name).toEqual(user.name); + }); + + it("should not allow name with only special characters", async () => { + const user = await userFactory(); + const { mutate } = makeClient({ ...user, auth: AuthType.Session }); + const newName = ".-"; + const { errors } = await mutate(EDIT_PROFILE, { + variables: { name: newName } + }); + const updatedUser = await prisma.user.findUniqueOrThrow({ + where: { id: user.id } + }); + expect(errors).toEqual([ + expect.objectContaining({ + message: `Le nom doit contenir au moins 2 lettres.`, + extensions: expect.objectContaining({ code: ErrorCode.BAD_USER_INPUT }) + }) + ]); + expect(updatedUser.name).toEqual(user.name); + }); afterAll(resetDatabase); it("should edit user profile", async () => { const user = await userFactory(); @@ -91,7 +128,7 @@ describe("mutation editProfile", () => { // Then expect(errors).toEqual([ expect.objectContaining({ - message: `The name cannot be an empty string`, + message: `Le nom doit contenir au moins 2 lettres.`, extensions: expect.objectContaining({ code: ErrorCode.BAD_USER_INPUT }) @@ -118,7 +155,7 @@ describe("mutation editProfile", () => { // Then expect(errors).toEqual([ expect.objectContaining({ - message: `The name cannot be an empty string`, + message: `Le nom doit contenir au moins 2 lettres.`, extensions: expect.objectContaining({ code: ErrorCode.BAD_USER_INPUT }) diff --git a/back/src/users/resolvers/mutations/__tests__/joinWithInvite.integration.ts b/back/src/users/resolvers/mutations/__tests__/joinWithInvite.integration.ts index a41be92b42..ba7665e139 100644 --- a/back/src/users/resolvers/mutations/__tests__/joinWithInvite.integration.ts +++ b/back/src/users/resolvers/mutations/__tests__/joinWithInvite.integration.ts @@ -16,6 +16,53 @@ const JOIN_WITH_INVITE = ` `; describe("joinWithInvite mutation", () => { + it("should not allow name with less than 2 letters", async () => { + const company = await companyFactory(); + const invitee = "shortname@td.io"; + const invitation = await prisma.userAccountHash.create({ + data: { + email: invitee, + companySiret: company.siret!, + role: "MEMBER", + hash: "hash-shortname" + } + }); + const { errors } = await mutate(JOIN_WITH_INVITE, { + variables: { + inviteHash: invitation.hash, + name: "A", + password: "P4a$$woRd_1234" + } + }); + expect(errors).not.toBeUndefined(); + expect(errors?.[0].message).toBe( + "Le nom doit contenir au moins 2 lettres." + ); + }); + + it("should not allow name with only special characters", async () => { + const company = await companyFactory(); + const invitee = "specialchars@td.io"; + const invitation = await prisma.userAccountHash.create({ + data: { + email: invitee, + companySiret: company.siret!, + role: "MEMBER", + hash: "hash-specialchars" + } + }); + const { errors } = await mutate(JOIN_WITH_INVITE, { + variables: { + inviteHash: invitation.hash, + name: ".-", + password: "P4a$$woRd_1234" + } + }); + expect(errors).not.toBeUndefined(); + expect(errors?.[0].message).toBe( + "Le nom doit contenir au moins 2 lettres." + ); + }); let mutate: ReturnType["mutate"]; beforeAll(() => { const testClient = makeClient(); @@ -146,7 +193,9 @@ describe("joinWithInvite mutation", () => { // Then expect(errors).toBeDefined(); - expect(errors[0].message).toContain("Le champ ne peut pas être vide."); + expect(errors[0].message).toContain( + "Le nom doit contenir au moins 2 lettres." + ); }); it("should accept other pending invitations", async () => { diff --git a/back/src/users/resolvers/mutations/__tests__/signup.integration.ts b/back/src/users/resolvers/mutations/__tests__/signup.integration.ts index ae8697237c..abaa9030a7 100644 --- a/back/src/users/resolvers/mutations/__tests__/signup.integration.ts +++ b/back/src/users/resolvers/mutations/__tests__/signup.integration.ts @@ -20,6 +20,49 @@ const SIGNUP = ` `; describe("Mutation.signup", () => { + it("should not allow name with less than 2 letters", async () => { + const user = { + email: "shortname@td.io", + name: "A", + phone: "06 00 00 00 00" + }; + const { errors } = await mutate>(SIGNUP, { + variables: { + userInfos: { + email: user.email, + password: viablePassword, + name: user.name, + phone: user.phone + } + } + }); + expect(errors).not.toBeUndefined(); + expect(errors?.[0].message).toBe( + "Le nom doit contenir au moins 2 lettres." + ); + }); + + it("should not allow name with only special characters", async () => { + const user = { + email: "specialchars@td.io", + name: ".-", + phone: "06 00 00 00 00" + }; + const { errors } = await mutate>(SIGNUP, { + variables: { + userInfos: { + email: user.email, + password: viablePassword, + name: user.name, + phone: user.phone + } + } + }); + expect(errors).not.toBeUndefined(); + expect(errors?.[0].message).toBe( + "Le nom doit contenir au moins 2 lettres." + ); + }); let mutate: ReturnType["mutate"]; beforeAll(() => { const testClient = makeClient(); @@ -91,7 +134,9 @@ describe("Mutation.signup", () => { // Then expect(errors).not.toBeUndefined(); - expect(errors?.[0].message).toBe("Le champ ne peut pas être vide."); + expect(errors?.[0].message).toBe( + "Le nom doit contenir au moins 2 lettres." + ); }); it("should return the same result if email already exist", async () => { diff --git a/back/src/users/resolvers/mutations/editProfile.ts b/back/src/users/resolvers/mutations/editProfile.ts index d8fe4963fe..4eb75be34e 100644 --- a/back/src/users/resolvers/mutations/editProfile.ts +++ b/back/src/users/resolvers/mutations/editProfile.ts @@ -23,8 +23,16 @@ export async function editProfileFn( const editProfileSchema = yup.object({ name: yup .string() - .test("empty", "The name cannot be an empty string", name => - isDefined(name) ? !!name && name.trim().length > 0 : true + .test( + "at-least-2-letters", + "Le nom doit contenir au moins 2 lettres.", + name => { + if (!isDefined(name)) return true; + if (!name) return false; + const trimmed = name.trim(); + const letterCount = (trimmed.match(/[\p{L}]/gu) || []).length; + return letterCount >= 2; + } ) .isSafeSSTI(), phone: yup.string(), diff --git a/back/src/users/resolvers/mutations/joinWithInvite.ts b/back/src/users/resolvers/mutations/joinWithInvite.ts index 73d0526001..2a4d2ec4be 100644 --- a/back/src/users/resolvers/mutations/joinWithInvite.ts +++ b/back/src/users/resolvers/mutations/joinWithInvite.ts @@ -15,9 +15,14 @@ const validationSchema = yup.object({ .required("Le nom est un champ requis") .isSafeSSTI() .test( - "not-empty-or-spaces", - "Le champ ne peut pas être vide.", - value => !!value && value.trim().length > 0 + "at-least-2-letters", + "Le nom doit contenir au moins 2 lettres.", + value => { + if (!value) return false; + const trimmed = value.trim(); + const letterCount = (trimmed.match(/[\p{L}]/gu) || []).length; + return letterCount >= 2; + } ), password: yup .string() diff --git a/back/src/users/resolvers/mutations/signup.ts b/back/src/users/resolvers/mutations/signup.ts index b2bfafe28a..69f9b4de92 100644 --- a/back/src/users/resolvers/mutations/signup.ts +++ b/back/src/users/resolvers/mutations/signup.ts @@ -17,9 +17,15 @@ function validateArgs(args: MutationSignupArgs) { .isSafeSSTI() .required("Vous devez saisir nom et prénom.") .test( - "not-empty-or-spaces", - "Le champ ne peut pas être vide.", - value => !!value && value.trim().length > 0 + "at-least-2-letters", + "Le nom doit contenir au moins 2 lettres.", + value => { + if (!value) return false; + const trimmed = value.trim(); + // Count letters (unicode) + const letterCount = (trimmed.match(/[\p{L}]/gu) || []).length; + return letterCount >= 2; + } ), email: yup .string()