diff --git a/src/__tests__/cron/validate-dossier.spec.ts b/src/__tests__/cron/validate-dossier.spec.ts index eef9e740..632ca7a7 100644 --- a/src/__tests__/cron/validate-dossier.spec.ts +++ b/src/__tests__/cron/validate-dossier.spec.ts @@ -91,19 +91,20 @@ describe("validateDossier", () => { }); it.each` - input | isValid - ${undefined} | ${true} - ${null} | ${true} - ${""} | ${true} - ${"non"} | ${false} - ${"doctolib"} | ${false} - ${"doctolib."} | ${false} - ${"doctolib. com"} | ${false} - ${"doctolib/com"} | ${false} - ${"shouldBeValid.com"} | ${false} - ${"https://valid.com"} | ${true} - ${"http://valid.com"} | ${true} - ${"http://VALID.com"} | ${true} + input | isValid + ${undefined} | ${true} + ${null} | ${true} + ${""} | ${true} + ${"non"} | ${false} + ${"doctolib"} | ${false} + ${"doctolib."} | ${false} + ${"doctolib. com"} | ${false} + ${"doctolib/com"} | ${false} + ${"example.org"} | ${true} + ${"www.example.org"} | ${true} + ${"https://example.org"} | ${true} + ${"http://example.org"} | ${true} + ${"http://EXAMPLE.org"} | ${true} `("should handle website error", async ({ input, isValid }) => { const errors = await validateDossier( { diff --git a/src/cron/demarchesSimplifiees.ts b/src/cron/demarchesSimplifiees.ts index 77652be5..f7ff16d2 100644 --- a/src/cron/demarchesSimplifiees.ts +++ b/src/cron/demarchesSimplifiees.ts @@ -21,6 +21,7 @@ import { } from "../services/psychologists"; import { AdeliData } from "../types/adeli"; import { Psychologist } from "../types/psychologist"; +import { urlExists } from "../utils/url-exists"; const limit = pLimit(5); @@ -111,6 +112,18 @@ export const validateDossier = async ( ); } + if (dossier.website) { + const urlWithProtocol: string = dossier.website?.startsWith("http") + ? dossier.website + : `https://${dossier.website}`; + const isUrlValid = await urlExists(urlWithProtocol); + if (!isUrlValid) { + errors.push( + `Le site web renseigné (${dossier.website}) n'est pas valide` + ); + } + } + const identifier = dossier.id?.toString() ?? dossier.email; const coordinates = await getAddressCoordinates(identifier, dossier.address); if (!coordinates) { diff --git a/src/services/__tests__/validation.spec.ts b/src/services/__tests__/validation.spec.ts index 37c21b5d..791f36e8 100644 --- a/src/services/__tests__/validation.spec.ts +++ b/src/services/__tests__/validation.spec.ts @@ -10,7 +10,6 @@ import { psychologistLastNameDoesNotMatchMessage, validatePsychologist, ValidationAdeliData, - webSiteIsInvalidMessage, } from "../demarchesSimplifiees/validate-psychologist"; describe("validatePsychologist", () => { @@ -159,37 +158,4 @@ describe("validatePsychologist", () => { candidateIsNotAPsychologistMessage("73") ); }); - - it("should fail when website is wrong", () => { - const invalidPsychologists: CandidatePsychologist[] = [ - { - ...validPsychologist, - website: "abcdefg", - }, - { - ...validPsychologist, - website: "www.example.org", - }, - ]; - - for (const invalidPsychologist of invalidPsychologists) { - const validation = validatePsychologist(invalidPsychologist, [adeliData]); - assertIsValidationError(validation); - - expect(validation.error.issues[0]?.message).toEqual( - webSiteIsInvalidMessage(invalidPsychologist.website) - ); - } - }); - - it("should work when website is ok", () => { - const { success } = validatePsychologist( - { - ...validPsychologist, - website: "https://example.org", - }, - [adeliData] - ); - expect(success).toBe(true); - }); }); diff --git a/src/services/demarchesSimplifiees/validate-psychologist.ts b/src/services/demarchesSimplifiees/validate-psychologist.ts index a804ae37..0036473d 100644 --- a/src/services/demarchesSimplifiees/validate-psychologist.ts +++ b/src/services/demarchesSimplifiees/validate-psychologist.ts @@ -70,14 +70,6 @@ export const validatePsychologist = ( return z .object({ psychologist: z.object({ - website: z - .optional( - z - .string() - .url(webSiteIsInvalidMessage(psychologist.website)) - .or(z.literal("")) - ) - .or(z.literal("")), lastName: validateWithAdeliData({ adeliData, message: psychologistLastNameDoesNotMatchMessage, @@ -126,6 +118,3 @@ export const candidateIsNotAPsychologistMessage = (code) => export const emailIsInvalidMessage = (email) => `L'email renseigné (${email}) n'est pas valide`; - -export const webSiteIsInvalidMessage = (url) => - `Le site web renseigné (${url}) n'est pas valide`; diff --git a/src/utils/url-exists.ts b/src/utils/url-exists.ts new file mode 100644 index 00000000..6f4fd1ec --- /dev/null +++ b/src/utils/url-exists.ts @@ -0,0 +1,51 @@ +// Source: https://stackoverflow.com/questions/35756479/does-jest-support-es6-import-export +import http from "http"; +import { URL } from "url"; + +// https://github.com/sindresorhus/is-url-superb/blob/main/index.js +function isUrl(str: string): boolean { + if (typeof str !== "string") { + return false; + } + + const trimmedStr = str.trim(); + if (trimmedStr.includes(" ")) { + return false; + } + + try { + new URL(str); + return true; + } catch { + return false; + } +} + +// https://github.com/Richienb/url-exist/blob/master/index.js +export function urlExists(url: string): Promise { + return new Promise((resolve) => { + if (!isUrl(url)) { + return resolve(false); + } + + const { host, pathname } = new URL(url.trim()); + + const options = { + method: "HEAD", + host, + path: pathname, + timeout: 2000, + }; + + const req = http.request(options, (res) => { + resolve(res.statusCode < 400); + }); + req.on("error", () => resolve(false)); + req.on("timeout", () => { + req.destroy(); + resolve(false); + }); + + req.end(); + }); +}