Skip to content

Commit

Permalink
fix(lbac-2298): ajout de la verif code postal dans les duplicates (#1697
Browse files Browse the repository at this point in the history
)

* fix(lbac-2298): ajout de la verif code postal dans les duplicates

* fix: improve tests

* fix: tests
  • Loading branch information
remy-auricoste authored Dec 11, 2024
1 parent 2b63393 commit f2944a5
Show file tree
Hide file tree
Showing 8 changed files with 254 additions and 71 deletions.
14 changes: 8 additions & 6 deletions .talismanrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
fileignoreconfig:
- filename: .infra/files/configs/mongodb/seed.gpg
checksum: c0d065fd21e851774bfb4f6dad2d4af7e339d38d3c62241ae40491ff4f8b8543
- filename: .infra/vault/vault.yml
checksum: 40717e8b277d9c9812c89d62fd3e09d0d70cdd0bb76a460f09486a31b6dd9cc7
- filename: cypress/e2e/manual/create-many-applications.cy.ts
Expand Down Expand Up @@ -37,6 +39,8 @@ fileignoreconfig:
checksum: 72502c3acc46f9b6903b69095518c3d865cf437f311ed14585da04d8440b4ac0
- filename: server/src/http/controllers/v2/jobs.controller.v2.test.ts
checksum: a99bfdcb5a8f4c66e9d055b44160adbc7ec2bcaf87209ff76fa9a9f674b9ca86
- filename: server/src/http/controllers/v2/jobs.controller.v2.ts
checksum: da8baa573172fa97c21f9123e04032718249dcec1194a51d7a1f1757070594ce
- filename: server/src/http/controllers/v3/jobs/jobs.controller.v3.test.ts
checksum: 4b24ff09463f476fd0b64bef104da6e17153ffdfff2d628c96b16d3ea33779f7
- filename: server/src/http/routes/appointmentRequest.controller.ts
Expand All @@ -57,6 +61,8 @@ fileignoreconfig:
checksum: 3cd111d8c109cfec357bae48af70d0cf5644d02cd2c4b9afc5b8aa07bccbd535
- filename: server/src/jobs/offrePartenaire/__snapshots__/importRHAlternance.test.ts.snap
checksum: 41e5eadcdaa0cb1eb95e4cb3859f4b7b1d176bbb95ee8451d46a6b2c9021c3fd
- filename: server/src/jobs/offrePartenaire/detectDuplicateJobPartners.test.ts
checksum: 82d624da5034d7e482cb7b2a99a134796e3a2b3c3a2a89a10da2e49242854c79
- filename: server/src/jobs/offrePartenaire/importFromUrlInXml.ts
checksum: 18bd149922151536ad0e239b082309697cee6046b33512d5eecc54b2d25b465a
- filename: server/src/jobs/offrePartenaire/importHelloWork.test.input.xml
Expand Down Expand Up @@ -85,6 +91,8 @@ fileignoreconfig:
checksum: a263b92a95e69de7efb856d73122b8f05ef98140c96dd0212213f26dce85056e
- filename: server/src/services/formulaire.service.ts
checksum: e93ff7ce146d35e70eedcbe9b66097750e7754650468a5ada6b0ed72f0fdbc49
- filename: server/src/services/jobs/jobOpportunity/__snapshots__/jobOpportunity.service.test.ts.snap
checksum: da3aa00d2041f36cd4b24eb4c98d04eb236cc80eab82a4e5aaf2e7e82549c613
- filename: server/src/services/jobs/jobOpportunity/jobOpportunity.service.test.ts
checksum: 8dd77bbcf687b50239b4958fbb957c069dda342551b76cc797af5349e0bd7024
- filename: server/src/services/recruteurLba.service.test.ts
Expand Down Expand Up @@ -239,12 +247,6 @@ fileignoreconfig:
checksum: 324cd501354cfff65447c2599c4cc8966aa8aac30dda7854623dd6f7f7b0d34e
- filename: yarn.lock
checksum: 21c8c4f9064194196622ad215768893c1756eec1cbc175efffcb1428d8821c9f
- filename: server/src/http/controllers/v2/jobs.controller.v2.ts
checksum: da8baa573172fa97c21f9123e04032718249dcec1194a51d7a1f1757070594ce
- filename: server/src/services/jobs/jobOpportunity/__snapshots__/jobOpportunity.service.test.ts.snap
checksum: da3aa00d2041f36cd4b24eb4c98d04eb236cc80eab82a4e5aaf2e7e82549c613
- filename: .infra/files/configs/mongodb/seed.gpg
checksum: c0d065fd21e851774bfb4f6dad2d4af7e339d38d3c62241ae40491ff4f8b8543
scopeconfig:
- scope: node
custom_patterns:
Expand Down
142 changes: 120 additions & 22 deletions server/src/jobs/offrePartenaire/detectDuplicateJobPartners.test.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { givenSomeComputedJobPartners } from "@tests/fixture/givenSomeComputedJobPartners"
import { useMongo } from "@tests/utils/mongo.test.utils"
import { saveRecruiter } from "@tests/utils/user.test.utils"
import { ObjectId } from "bson"
import { RECRUITER_STATUS } from "shared/constants"
import { generateJobFixture, generateRecruiterFixture } from "shared/fixtures/recruiter.fixture"
import { JOB_STATUS } from "shared/models"
import { JOBPARTNERS_LABEL } from "shared/models/jobsPartners.model"
import { JOB_PARTNER_BUSINESS_ERROR } from "shared/models/jobsPartnersComputed.model"
import { beforeEach, describe, expect, it } from "vitest"

import { getPairs } from "@/common/utils/array"
import { getDbCollection } from "@/common/utils/mongodbUtils"

import { checkSimilarity, detectDuplicateJobPartners } from "./detectDuplicateJobPartners"
import { checkSimilarity, detectDuplicateJobPartners, isCanonicalForDuplicate, OfferRef } from "./detectDuplicateJobPartners"

const siret = "42476141900045"

Expand All @@ -29,14 +31,18 @@ describe("detectDuplicateJobPartners", () => {
// given
await givenSomeComputedJobPartners([
{
_id: new ObjectId("60646425184afd00e017c1ab"),
partner_label: JOBPARTNERS_LABEL.HELLOWORK,
workplace_siret: siret,
offer_title: offerTitle,
workplace_address_zipcode: "75007",
},
{
_id: new ObjectId("62646425184afd00e017c1ab"),
partner_label: JOBPARTNERS_LABEL.RH_ALTERNANCE,
workplace_siret: siret,
offer_title: offerTitle,
workplace_address_zipcode: "75007",
},
])
// when
Expand All @@ -51,27 +57,36 @@ describe("detectDuplicateJobPartners", () => {
reason: "identical workplace_siret, identical offer_title",
},
])
expect.soft(job.business_error).toEqual(null)
expect.soft(job2.duplicates).toEqual([
{
otherOfferId: job._id,
collectionName: "computed_jobs_partners",
reason: "identical workplace_siret, identical offer_title",
},
])
expect.soft(job2.business_error).toEqual(JOB_PARTNER_BUSINESS_ERROR.DUPLICATE)
})
it("should detect a duplicate with an exact match in the offer title and the siret (recruiter)", async () => {
// given
await givenSomeComputedJobPartners([
{
_id: new ObjectId("60646425184afd00e017c1ab"),
partner_label: JOBPARTNERS_LABEL.RH_ALTERNANCE,
workplace_siret: siret,
offer_title: offerTitle,
workplace_address_zipcode: "75007",
},
])
const recruiter = await saveRecruiter(
generateRecruiterFixture({
_id: new ObjectId("62646425184afd00e017c1ab"),
establishment_siret: siret,
status: RECRUITER_STATUS.ACTIF,
address_detail: {
...generateRecruiterFixture().address_detail,
code_postal: "75007",
},
jobs: [
generateJobFixture({
rome_appellation_label: offerTitle,
Expand All @@ -92,6 +107,64 @@ describe("detectDuplicateJobPartners", () => {
reason: "identical workplace_siret, identical offer_title",
},
])
expect.soft(job.business_error).toEqual(JOB_PARTNER_BUSINESS_ERROR.DUPLICATE)
})
it("should not detect a duplicate with an exact match in the offer title and the siret but a different zip code (job_partner)", async () => {
// given
await givenSomeComputedJobPartners([
{
partner_label: JOBPARTNERS_LABEL.HELLOWORK,
workplace_siret: siret,
offer_title: offerTitle,
workplace_address_zipcode: "75002",
},
{
partner_label: JOBPARTNERS_LABEL.RH_ALTERNANCE,
workplace_siret: siret,
offer_title: offerTitle,
workplace_address_zipcode: "75007",
},
])
// when
await detectDuplicateJobPartners()
// then
const jobs = await getDbCollection("computed_jobs_partners").find({}).toArray()
const [job, job2] = jobs
expect.soft(job.duplicates).toEqual([])
expect.soft(job2.duplicates).toEqual([])
})
it("should not detect a duplicate with an exact match in the offer title and the siret but a different zip code (recruiter)", async () => {
// given
await givenSomeComputedJobPartners([
{
partner_label: JOBPARTNERS_LABEL.RH_ALTERNANCE,
workplace_siret: siret,
offer_title: offerTitle,
workplace_address_zipcode: "75002",
},
])
await saveRecruiter(
generateRecruiterFixture({
establishment_siret: siret,
status: RECRUITER_STATUS.ACTIF,
address_detail: {
...generateRecruiterFixture().address_detail,
code_postal: "75007",
},
jobs: [
generateJobFixture({
rome_appellation_label: offerTitle,
job_status: JOB_STATUS.ACTIVE,
}),
],
})
)
// when
await detectDuplicateJobPartners()
// then
const jobs = await getDbCollection("computed_jobs_partners").find({}).toArray()
const [job] = jobs
expect.soft(job.duplicates).toEqual([])
})
describe("checkSimilarity", () => {
it("should not detect when one string is empty", async () => {
Expand All @@ -106,29 +179,54 @@ describe("detectDuplicateJobPartners", () => {
expect.soft(checkSimilarity(offerTitle, offerTitle)).toEqual("identical")
})

const coiffeurGroup = ["H/F Coiffeur", "Coiffeur H/F", "Coiffeur", "Coiffeuse", "Coiffeur.se", "Coiffeur (H/F)"]
it.each(getPairs(coiffeurGroup))("should detect similar strings: %s === %s", (item1, item2) => {
expect.soft(checkSimilarity(item1, item2)).toEqual(expect.stringContaining("similar"))
})

const directionGroup = ["Direction interministérielle du numérique", "Dir. interministérielle du numérique"]
it.each(getPairs(directionGroup))("should detect similar strings: %s === %s", (item1, item2) => {
expect.soft(checkSimilarity(item1, item2)).toEqual(expect.stringContaining("similar"))
})

const sigleGroup = ["MFR la pignerie", "Maison familiale rurale la pignerie"]
it.each(getPairs(sigleGroup))("should detect similar strings: %s === %s", (item1, item2) => {
expect.soft(checkSimilarity(item1, item2)).toEqual(expect.stringContaining("similar"))
})

const sensSimilaireGroup = ["Chargé de mission en Ressources Humaines (RH) (H/F)", "Chargé de mission RH", "Chargé.e de mission RH"]
it.each(getPairs(sensSimilaireGroup))("should detect similar strings: %s === %s", (item1, item2) => {
expect.soft(checkSimilarity(item1, item2)).toEqual(expect.stringContaining("similar"))
const groups = [
["H/F Coiffeur", "Coiffeur H/F", "Coiffeur", "Coiffeuse", "Coiffeur.se", "Coiffeur (H/F)"],
["Direction interministérielle du numérique", "Dir. interministérielle du numérique"],
["MFR la pignerie", "Maison familiale rurale la pignerie"],
["Chargé de mission en Ressources Humaines (RH) (H/F)", "Chargé de mission RH", "Chargé.e de mission RH"],
// ["Drysoft", "dyrsoft"]
]
groups.forEach((group) => {
it.each(getPairs(group))("should detect similar strings: %s === %s", (item1, item2) => {
expect.soft(checkSimilarity(item1, item2)).toEqual(expect.stringContaining("similar"))
})
})
})
describe("isCanonicalForDuplicate", () => {
// cf https://www.math.univ-toulouse.fr/~msablik/Cours/MathDiscretes/Slide4-Relation.pdf
describe("doit être une relation d ordre", () => {
const offerRefs: OfferRef[] = [
{ collectionName: "recruiters", _id: new ObjectId("60646425184afd00e017c1ab") },
{ collectionName: "recruiters", _id: new ObjectId("642605b7c52247005267027e") },
{ collectionName: "recruiters", _id: new ObjectId("682605b7c52247005267027e") },
{ collectionName: "computed_jobs_partners", _id: new ObjectId("6525231132fe045f240e4bcd") },
{ collectionName: "computed_jobs_partners", _id: new ObjectId("67347c0c9af58cb6e6ebbb65") },
{ collectionName: "computed_jobs_partners", _id: new ObjectId("63347c0c9af58cb6e6ebbb65") },
]

const typoGroup = ["Drysoft", "dyrsoft"]
it.skip.each(getPairs(typoGroup))("should detect similar strings: %s === %s", (item1, item2) => {
expect.soft(checkSimilarity(item1, item2)).toEqual(expect.stringContaining("similar"))
it("la relation doit être reflexive (xRx)", () => {
offerRefs.forEach((offerRef) => expect(isCanonicalForDuplicate(offerRef, offerRef)).toBe(true))
})
it("la relation doit être antisymétrique (xRy and yRx => x = y)", () => {
getPairs(offerRefs)
.filter(([item, item2]) => isCanonicalForDuplicate(item, item2) && isCanonicalForDuplicate(item2, item))
.forEach(([item, item2]) => expect(item).toBe(item2))
})
it("la relation doit être transitive (xRy and yRz => xRz)", () => {
const validPairs = getPairs(offerRefs).flatMap(([item, item2]) => {
const results: [OfferRef, OfferRef][] = []
if (isCanonicalForDuplicate(item, item2)) {
results.push([item, item2])
}
if (isCanonicalForDuplicate(item2, item)) {
results.push([item2, item])
}
return results
})
getPairs(validPairs)
.filter(([[_item, item2], [item3, _item4]]) => item2 === item3)
.forEach(([[item, _item2], [_item3, item4]]) => expect(isCanonicalForDuplicate(item, item4)).toBe(true))
})
})
})
})
Loading

0 comments on commit f2944a5

Please sign in to comment.