@proconnect-gouv/proconnect.identite 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +0 -2
  3. package/dist/data/certification/categories-juridiques-sources.d.ts +8 -0
  4. package/dist/data/certification/categories-juridiques-sources.d.ts.map +1 -0
  5. package/dist/data/certification/categories-juridiques-sources.js +743 -0
  6. package/dist/data/certification/index.d.ts +1 -0
  7. package/dist/data/certification/index.d.ts.map +1 -1
  8. package/dist/data/certification/index.js +1 -0
  9. package/dist/errors/index.d.ts +0 -9
  10. package/dist/errors/index.d.ts.map +1 -1
  11. package/dist/errors/index.js +0 -18
  12. package/dist/managers/certification/certification-score.d.ts +11 -1
  13. package/dist/managers/certification/certification-score.d.ts.map +1 -1
  14. package/dist/managers/certification/certification-score.js +17 -8
  15. package/dist/managers/certification/index.d.ts +1 -1
  16. package/dist/managers/certification/index.d.ts.map +1 -1
  17. package/dist/managers/certification/index.js +1 -1
  18. package/dist/managers/certification/process-certification-dirigeant.d.ts +80 -0
  19. package/dist/managers/certification/process-certification-dirigeant.d.ts.map +1 -0
  20. package/dist/managers/certification/process-certification-dirigeant.js +121 -0
  21. package/dist/managers/organization/adapters/api_entreprise.d.ts +2 -1
  22. package/dist/managers/organization/adapters/api_entreprise.d.ts.map +1 -1
  23. package/dist/managers/organization/adapters/api_entreprise.js +3 -1
  24. package/dist/managers/organization/force-join-organization.d.ts +1 -1
  25. package/dist/managers/organization/force-join-organization.d.ts.map +1 -1
  26. package/dist/managers/organization/force-join-organization.js +3 -4
  27. package/dist/managers/user/assign-user-verification-type-to-domain.d.ts.map +1 -1
  28. package/dist/managers/user/assign-user-verification-type-to-domain.js +6 -6
  29. package/dist/repositories/organization/find-by-user-id.d.ts +1 -1
  30. package/dist/repositories/organization/get-users-by-organization.d.ts +1 -1
  31. package/dist/repositories/organization/link-user-to-organization.d.ts +1 -1
  32. package/dist/repositories/organization/upsert.d.ts.map +1 -1
  33. package/dist/repositories/organization/upsert.js +8 -3
  34. package/dist/repositories/user/update-user-organization-link.d.ts +1 -1
  35. package/dist/services/organization/index.d.ts +1 -0
  36. package/dist/services/organization/index.d.ts.map +1 -1
  37. package/dist/services/organization/index.js +1 -0
  38. package/dist/services/organization/is-entreprise-unipersonnelle.d.ts +3 -3
  39. package/dist/services/organization/is-entreprise-unipersonnelle.d.ts.map +1 -1
  40. package/dist/services/organization/is-entreprise-unipersonnelle.js +4 -4
  41. package/dist/services/organization/is-organization-covered-by-certification-dirigeant.d.ts +3 -0
  42. package/dist/services/organization/is-organization-covered-by-certification-dirigeant.d.ts.map +1 -0
  43. package/dist/services/organization/is-organization-covered-by-certification-dirigeant.js +2 -0
  44. package/dist/services/organization/is-public-service.d.ts.map +1 -1
  45. package/dist/services/organization/is-public-service.js +16 -6
  46. package/dist/services/organization/is-syndicat-communal.d.ts.map +1 -1
  47. package/dist/services/organization/is-syndicat-communal.js +1 -0
  48. package/dist/types/franceconnect.d.ts.map +1 -1
  49. package/dist/types/franceconnect.js +2 -2
  50. package/dist/types/{dirigeant.d.ts → identity-vector.d.ts} +2 -1
  51. package/dist/types/identity-vector.d.ts.map +1 -0
  52. package/dist/types/{dirigeant.js → identity-vector.js} +8 -0
  53. package/dist/types/index.d.ts +1 -1
  54. package/dist/types/index.d.ts.map +1 -1
  55. package/dist/types/index.js +1 -1
  56. package/dist/types/organization-info.d.ts +2 -1
  57. package/dist/types/organization-info.d.ts.map +1 -1
  58. package/dist/types/organization-info.js +1 -0
  59. package/dist/types/organization.d.ts +1 -0
  60. package/dist/types/organization.d.ts.map +1 -1
  61. package/dist/types/user-organization-link.d.ts +31 -24
  62. package/dist/types/user-organization-link.d.ts.map +1 -1
  63. package/dist/types/user-organization-link.js +15 -8
  64. package/package.json +2 -2
  65. package/src/data/certification/categories-juridiques-sources.ts +811 -0
  66. package/src/data/certification/index.ts +1 -0
  67. package/src/errors/index.ts +0 -21
  68. package/src/managers/certification/certification-score.test.ts +69 -33
  69. package/src/managers/certification/certification-score.ts +20 -9
  70. package/src/managers/certification/index.ts +1 -1
  71. package/src/managers/certification/{is-organization-dirigeant.test.ts → process-certification-dirigeant.test.ts} +62 -64
  72. package/src/managers/certification/{is-organization-dirigeant.ts → process-certification-dirigeant.ts} +115 -74
  73. package/src/managers/organization/adapters/api_entreprise.test.ts.snapshot +4 -0
  74. package/src/managers/organization/adapters/api_entreprise.ts +3 -0
  75. package/src/managers/organization/force-join-organization.ts +3 -6
  76. package/src/managers/organization/get-organization-info.test.ts.snapshot +3 -0
  77. package/src/managers/organization/mark-domain-as-verified.test.ts +1 -1
  78. package/src/managers/user/assign-user-verification-type-to-domain.ts +6 -6
  79. package/src/repositories/organization/find-by-id.test.ts +1 -0
  80. package/src/repositories/organization/find-by-user-id.test.ts.snapshot +3 -0
  81. package/src/repositories/organization/get-by-id.test.ts +1 -0
  82. package/src/repositories/organization/link-user-to-organization.test.ts +3 -2
  83. package/src/repositories/organization/link-user-to-organization.test.ts.snapshot +2 -2
  84. package/src/repositories/organization/upsert.ts +8 -1
  85. package/src/repositories/organization/upset.test.ts +2 -0
  86. package/src/services/organization/index.ts +1 -0
  87. package/src/services/organization/is-entreprise-unipersonnelle.ts +5 -5
  88. package/src/services/organization/is-organization-covered-by-certification-dirigeant.test.ts +43 -0
  89. package/src/services/organization/is-organization-covered-by-certification-dirigeant.ts +9 -0
  90. package/src/services/organization/is-public-service.test.ts +1 -1
  91. package/src/services/organization/is-public-service.ts +20 -7
  92. package/src/services/organization/is-syndicat-communal.ts +1 -0
  93. package/src/types/franceconnect.ts +6 -7
  94. package/src/types/{dirigeant.ts → identity-vector.ts} +9 -0
  95. package/src/types/index.ts +1 -1
  96. package/src/types/organization-info.ts +1 -0
  97. package/src/types/organization.ts +1 -0
  98. package/src/types/user-organization-link.ts +17 -11
  99. package/tsconfig.lib.tsbuildinfo +1 -1
  100. package/dist/managers/certification/is-organization-dirigeant.d.ts +0 -45
  101. package/dist/managers/certification/is-organization-dirigeant.d.ts.map +0 -1
  102. package/dist/managers/certification/is-organization-dirigeant.js +0 -97
  103. package/dist/types/dirigeant.d.ts.map +0 -1
@@ -1,4 +1,5 @@
1
1
  //
2
2
 
3
+ export * from "./categories-juridiques-sources.js";
3
4
  export * from "./commune-code-conversion.js";
4
5
  export * from "./country-iso-to-cog.js";
@@ -1,12 +1,5 @@
1
1
  //
2
2
 
3
- export class InvalidCertificationError extends Error {
4
- constructor(message?: string, options?: ErrorOptions) {
5
- super(message, options);
6
- this.name = "InvalidCertificationError";
7
- }
8
- }
9
-
10
3
  export class InvalidSiretError extends Error {
11
4
  constructor(message?: string, options?: ErrorOptions) {
12
5
  super(message, options);
@@ -21,13 +14,6 @@ export class NotFoundError extends Error {
21
14
  }
22
15
  }
23
16
 
24
- export class OrganizationNotActiveError extends Error {
25
- constructor(message?: string, options?: ErrorOptions) {
26
- super(message, options);
27
- this.name = "OrganizationNotActiveError";
28
- }
29
- }
30
-
31
17
  export class OrganizationNotFoundError extends Error {
32
18
  constructor(message?: string, options?: ErrorOptions) {
33
19
  super(message, options);
@@ -41,10 +27,3 @@ export class UserNotFoundError extends Error {
41
27
  this.name = "UserNotFoundError";
42
28
  }
43
29
  }
44
-
45
- export class ModerationNotFoundError extends Error {
46
- constructor(message?: string, options?: ErrorOptions) {
47
- super(message, options);
48
- this.name = "ModerationNotFoundError";
49
- }
50
- }
@@ -9,7 +9,7 @@ import { certificationScore } from "./certification-score.js";
9
9
  describe("certification scoring", () => {
10
10
  it("perfect match - all 5 criteria", () => {
11
11
  // All criteria match: family name, first name, gender, birthdate, birthplace
12
- assert.equal(
12
+ assert.deepStrictEqual(
13
13
  certificationScore(
14
14
  {
15
15
  birthcountry: null,
@@ -28,13 +28,19 @@ describe("certification scoring", () => {
28
28
  given_name: "Stéphane",
29
29
  },
30
30
  ),
31
- 5,
31
+ new Set([
32
+ "family_name",
33
+ "first_name",
34
+ "gender",
35
+ "birth_date",
36
+ "birth_place",
37
+ ]),
32
38
  );
33
39
  });
34
40
 
35
41
  it("family name mismatch", () => {
36
- // Different family name = -1 point
37
- assert.equal(
42
+ // Different family name = missing family_name
43
+ assert.deepStrictEqual(
38
44
  certificationScore(
39
45
  {
40
46
  birthcountry: null,
@@ -53,13 +59,13 @@ describe("certification scoring", () => {
53
59
  given_name: "Stéphane",
54
60
  },
55
61
  ),
56
- 4,
62
+ new Set(["first_name", "gender", "birth_date", "birth_place"]),
57
63
  );
58
64
  });
59
65
 
60
66
  it("different birthdate", () => {
61
- // Different birthdate = -1 point
62
- assert.equal(
67
+ // Different birthdate = missing birth_date
68
+ assert.deepStrictEqual(
63
69
  certificationScore(
64
70
  {
65
71
  birthcountry: null,
@@ -78,13 +84,13 @@ describe("certification scoring", () => {
78
84
  given_name: "Stéphane",
79
85
  },
80
86
  ),
81
- 4,
87
+ new Set(["family_name", "first_name", "gender", "birth_place"]),
82
88
  );
83
89
  });
84
90
 
85
91
  it("different birthplace", () => {
86
- // Different birthplace = -1 point
87
- assert.equal(
92
+ // Different birthplace = missing birth_place
93
+ assert.deepStrictEqual(
88
94
  certificationScore(
89
95
  {
90
96
  birthcountry: null,
@@ -103,13 +109,13 @@ describe("certification scoring", () => {
103
109
  given_name: "Stéphane",
104
110
  },
105
111
  ),
106
- 4,
112
+ new Set(["family_name", "first_name", "gender", "birth_date"]),
107
113
  );
108
114
  });
109
115
 
110
116
  it("swapped first and family names", () => {
111
- // Both names don't match = -2 points
112
- assert.equal(
117
+ // Both names don't match = missing both name criteria
118
+ assert.deepStrictEqual(
113
119
  certificationScore(
114
120
  {
115
121
  birthcountry: null,
@@ -128,13 +134,13 @@ describe("certification scoring", () => {
128
134
  given_name: "Stéphane",
129
135
  },
130
136
  ),
131
- 3,
137
+ new Set(["gender", "birth_date", "birth_place"]),
132
138
  );
133
139
  });
134
140
 
135
141
  it("different gender still matches when source gender is null", () => {
136
- // Gender is null in source = still gets point
137
- assert.equal(
142
+ // Gender is null in source = still gets gender match
143
+ assert.deepStrictEqual(
138
144
  certificationScore(
139
145
  {
140
146
  birthcountry: null,
@@ -153,13 +159,19 @@ describe("certification scoring", () => {
153
159
  given_name: "Stéphane",
154
160
  },
155
161
  ),
156
- 5,
162
+ new Set([
163
+ "family_name",
164
+ "first_name",
165
+ "gender",
166
+ "birth_date",
167
+ "birth_place",
168
+ ]),
157
169
  );
158
170
  });
159
171
 
160
172
  it("different gender when source has gender", () => {
161
- // Different gender = -1 point
162
- assert.equal(
173
+ // Different gender = missing gender
174
+ assert.deepStrictEqual(
163
175
  certificationScore(
164
176
  {
165
177
  birthcountry: null,
@@ -178,13 +190,13 @@ describe("certification scoring", () => {
178
190
  given_name: "Stéphane",
179
191
  },
180
192
  ),
181
- 4,
193
+ new Set(["family_name", "first_name", "birth_date", "birth_place"]),
182
194
  );
183
195
  });
184
196
 
185
197
  it("matches first name with hyphen normalization", () => {
186
- // Jean-Pierre and Jean Pierre should match (first name is "Jean")
187
- assert.equal(
198
+ // Jean-Pierre and Jean Marc should match (first name is "Jean")
199
+ assert.deepStrictEqual(
188
200
  certificationScore(
189
201
  {
190
202
  birthcountry: null,
@@ -203,13 +215,19 @@ describe("certification scoring", () => {
203
215
  given_name: "Jean Marc",
204
216
  },
205
217
  ),
206
- 5,
218
+ new Set([
219
+ "family_name",
220
+ "first_name",
221
+ "gender",
222
+ "birth_date",
223
+ "birth_place",
224
+ ]),
207
225
  );
208
226
  });
209
227
 
210
228
  it("birthplace with commune code conversion", () => {
211
229
  // 75050 should convert to 92050
212
- assert.equal(
230
+ assert.deepStrictEqual(
213
231
  certificationScore(
214
232
  {
215
233
  birthcountry: null,
@@ -228,13 +246,19 @@ describe("certification scoring", () => {
228
246
  given_name: "Stéphane",
229
247
  },
230
248
  ),
231
- 5,
249
+ new Set([
250
+ "family_name",
251
+ "first_name",
252
+ "gender",
253
+ "birth_date",
254
+ "birth_place",
255
+ ]),
232
256
  );
233
257
  });
234
258
 
235
259
  it("null birthplace in source still matches", () => {
236
- // Null birthplace in source = still gets point
237
- assert.equal(
260
+ // Null birthplace in source = still gets birth_place match
261
+ assert.deepStrictEqual(
238
262
  certificationScore(
239
263
  {
240
264
  birthcountry: null,
@@ -253,13 +277,19 @@ describe("certification scoring", () => {
253
277
  given_name: "Stéphane",
254
278
  },
255
279
  ),
256
- 5,
280
+ new Set([
281
+ "family_name",
282
+ "first_name",
283
+ "gender",
284
+ "birth_date",
285
+ "birth_place",
286
+ ]),
257
287
  );
258
288
  });
259
289
 
260
290
  it("foreign person with matching country", () => {
261
291
  // For foreigners, check country code
262
- assert.equal(
292
+ assert.deepStrictEqual(
263
293
  certificationScore(
264
294
  {
265
295
  birthcountry: "99136",
@@ -278,13 +308,19 @@ describe("certification scoring", () => {
278
308
  given_name: "Patrick",
279
309
  },
280
310
  ),
281
- 5,
311
+ new Set([
312
+ "family_name",
313
+ "first_name",
314
+ "gender",
315
+ "birth_date",
316
+ "birth_country",
317
+ ]),
282
318
  );
283
319
  });
284
320
 
285
321
  it("foreign person with different country", () => {
286
- // Different country = -1 point
287
- assert.equal(
322
+ // Different country = missing birth_country
323
+ assert.deepStrictEqual(
288
324
  certificationScore(
289
325
  {
290
326
  birthcountry: "99136", // Ireland
@@ -303,7 +339,7 @@ describe("certification scoring", () => {
303
339
  given_name: "Patrick",
304
340
  },
305
341
  ),
306
- 4,
342
+ new Set(["family_name", "first_name", "gender", "birth_date"]),
307
343
  );
308
344
  });
309
345
  });
@@ -1,11 +1,22 @@
1
1
  //
2
2
 
3
3
  import type { IdentityVector } from "#src/types";
4
+ import z from "zod/v4";
4
5
  import { convertCommuneCode } from "./birthplace-conversion.js";
5
6
  import { extractFirstName, normalizeText } from "./normalize.js";
6
7
 
7
8
  //
8
9
 
10
+ export const MatchCriteria = z.enum([
11
+ "birth_country",
12
+ "birth_date",
13
+ "birth_place",
14
+ "family_name",
15
+ "first_name",
16
+ "gender",
17
+ ]);
18
+ export type MatchCriteria = z.output<typeof MatchCriteria>;
19
+
9
20
  /**
10
21
  * Calculates a certification score between identity from FranceConnect (IdentitePivot)
11
22
  * and executive identity from business registries (SourceDirigeant).
@@ -28,8 +39,8 @@ import { extractFirstName, normalizeText } from "./normalize.js";
28
39
  export function certificationScore(
29
40
  identitePivot: IdentityVector,
30
41
  sourceDirigeant: IdentityVector,
31
- ): number {
32
- let score = 0;
42
+ ) {
43
+ const matches = new Set<MatchCriteria>();
33
44
 
34
45
  // 1. Family name match (after normalization)
35
46
  const normalizedFamilyNamePivot = normalizeText(identitePivot.family_name);
@@ -40,7 +51,7 @@ export function certificationScore(
40
51
  normalizedFamilyNameSource &&
41
52
  normalizedFamilyNamePivot === normalizedFamilyNameSource
42
53
  ) {
43
- score += 1;
54
+ matches.add(MatchCriteria.enum.family_name);
44
55
  }
45
56
 
46
57
  // 2. First name match (first name only, after normalization)
@@ -48,7 +59,7 @@ export function certificationScore(
48
59
  const firstNameSource = extractFirstName(sourceDirigeant.given_name);
49
60
 
50
61
  if (firstNamePivot && firstNameSource && firstNamePivot === firstNameSource) {
51
- score += 1;
62
+ matches.add(MatchCriteria.enum.first_name);
52
63
  }
53
64
 
54
65
  // 3. Gender match (match or absent in source)
@@ -56,7 +67,7 @@ export function certificationScore(
56
67
  !sourceDirigeant.gender ||
57
68
  identitePivot.gender === sourceDirigeant.gender
58
69
  ) {
59
- score += 1;
70
+ matches.add(MatchCriteria.enum.gender);
60
71
  }
61
72
 
62
73
  // 4. Birth date match (exact match)
@@ -65,7 +76,7 @@ export function certificationScore(
65
76
  sourceDirigeant.birthdate &&
66
77
  identitePivot.birthdate.getTime() === sourceDirigeant.birthdate.getTime()
67
78
  ) {
68
- score += 1;
79
+ matches.add(MatchCriteria.enum.birth_date);
69
80
  }
70
81
 
71
82
  // 5. Birth place match (different logic for French vs foreigners)
@@ -79,7 +90,7 @@ export function certificationScore(
79
90
 
80
91
  // Match if equal or if source has no commune data
81
92
  if (!communeSource || (communePivot && communePivot === communeSource)) {
82
- score += 1;
93
+ matches.add(MatchCriteria.enum.birth_place);
83
94
  }
84
95
  } else {
85
96
  // For foreigners: check country code (with conversion if needed from RNE)
@@ -89,9 +100,9 @@ export function certificationScore(
89
100
  sourceDirigeant.birthcountry &&
90
101
  identitePivot.birthcountry === sourceDirigeant.birthcountry
91
102
  ) {
92
- score += 1;
103
+ matches.add(MatchCriteria.enum.birth_country);
93
104
  }
94
105
  }
95
106
 
96
- return score;
107
+ return matches;
97
108
  }
@@ -1,4 +1,4 @@
1
1
  //
2
2
 
3
3
  export * from "./certification-score.js";
4
- export * from "./is-organization-dirigeant.js";
4
+ export * from "./process-certification-dirigeant.js";
@@ -1,6 +1,5 @@
1
1
  //
2
2
 
3
- import { InvalidCertificationError, NotFoundError } from "#src/errors";
4
3
  import { IdentityVectorSchema } from "#src/types";
5
4
  import {
6
5
  LiElJonsonFranceConnectUserInfo,
@@ -24,13 +23,13 @@ import {
24
23
  } from "@proconnect-gouv/proconnect.registre_national_entreprises/testing/seed";
25
24
  import assert from "node:assert/strict";
26
25
  import { describe, it } from "node:test";
27
- import { isOrganizationDirigeantFactory } from "./is-organization-dirigeant.js";
26
+ import { processCertificationDirigeantFactory } from "./process-certification-dirigeant.js";
28
27
 
29
28
  //
30
29
 
31
- describe("isOrganizationDirigeantFactory", () => {
30
+ describe("processCertificationDirigeantFactory", () => {
32
31
  it("should recognize a user as executive of a auto-entrepreneur", async () => {
33
- const isOrganizationDirigeant = isOrganizationDirigeantFactory({
32
+ const processCertificationDirigeant = processCertificationDirigeantFactory({
34
33
  ApiEntrepriseInfogreffeRepository: {
35
34
  findMandatairesSociauxBySiren: () => Promise.reject(new Error("💣")),
36
35
  },
@@ -40,22 +39,27 @@ describe("isOrganizationDirigeantFactory", () => {
40
39
  InseeApiRepository: {
41
40
  findBySiren: () => Promise.resolve(RogalDornEstablishment),
42
41
  },
43
- FranceConnectApiRepository: {
44
- getFranceConnectUserInfo: () =>
45
- Promise.resolve(RogalDornFranceConnectUserInfo),
46
- },
47
42
  });
48
43
 
49
- const isDirigeant = await isOrganizationDirigeant(rogal_dorn_org_info, 1);
44
+ const certificationDirigeantResult = await processCertificationDirigeant(
45
+ rogal_dorn_org_info,
46
+ RogalDornFranceConnectUserInfo,
47
+ );
50
48
 
51
- assert.deepEqual(isDirigeant, {
49
+ assert.deepEqual(certificationDirigeantResult, {
52
50
  cause: "exact_match",
53
51
  details: {
54
52
  dirigeant: {
55
53
  ...IdentityVectorSchema.parse(RogalDornFranceConnectUserInfo),
56
54
  birthplace: null, // INSEE adapter returns null for foreigners
57
55
  },
58
- score: 5,
56
+ matches: new Set([
57
+ "family_name",
58
+ "first_name",
59
+ "gender",
60
+ "birth_date",
61
+ "birth_country",
62
+ ]),
59
63
  identity: IdentityVectorSchema.parse(RogalDornFranceConnectUserInfo),
60
64
  source: "api.insee.fr/api-sirene/private",
61
65
  },
@@ -64,7 +68,7 @@ describe("isOrganizationDirigeantFactory", () => {
64
68
  });
65
69
 
66
70
  it("should not match another mandataire", async () => {
67
- const isOrganizationDirigeant = isOrganizationDirigeantFactory({
71
+ const processCertificationDirigeant = processCertificationDirigeantFactory({
68
72
  ApiEntrepriseInfogreffeRepository: {
69
73
  findMandatairesSociauxBySiren: () => Promise.reject(new Error("💣")),
70
74
  },
@@ -74,22 +78,21 @@ describe("isOrganizationDirigeantFactory", () => {
74
78
  InseeApiRepository: {
75
79
  findBySiren: () => Promise.resolve(LiElJonsonEstablishment),
76
80
  },
77
- FranceConnectApiRepository: {
78
- getFranceConnectUserInfo: () =>
79
- Promise.resolve(RogalDornFranceConnectUserInfo),
80
- },
81
81
  });
82
82
 
83
- const isDirigeant = await isOrganizationDirigeant(rogal_dorn_org_info, 1);
83
+ const certificationDirigeantResult = await processCertificationDirigeant(
84
+ rogal_dorn_org_info,
85
+ RogalDornFranceConnectUserInfo,
86
+ );
84
87
 
85
- assert.deepEqual(isDirigeant, {
88
+ assert.deepEqual(certificationDirigeantResult, {
86
89
  cause: "below_threshold",
87
90
  details: {
88
91
  dirigeant: {
89
92
  ...IdentityVectorSchema.parse(LiElJonsonFranceConnectUserInfo),
90
93
  birthcountry: null, // INSEE adapter returns null for French people
91
94
  },
92
- score: 1,
95
+ matches: new Set(["gender"]),
93
96
  identity: IdentityVectorSchema.parse(RogalDornFranceConnectUserInfo),
94
97
  source: "api.insee.fr/api-sirene/private",
95
98
  },
@@ -98,7 +101,7 @@ describe("isOrganizationDirigeantFactory", () => {
98
101
  });
99
102
 
100
103
  it("should match Rogal Dorn among the executive of Papillon in RNE", async () => {
101
- const isOrganizationDirigeant = isOrganizationDirigeantFactory({
104
+ const processCertificationDirigeant = processCertificationDirigeantFactory({
102
105
  ApiEntrepriseInfogreffeRepository: {
103
106
  findMandatairesSociauxBySiren: () => Promise.reject(new Error("💣")),
104
107
  },
@@ -109,22 +112,27 @@ describe("isOrganizationDirigeantFactory", () => {
109
112
  InseeApiRepository: {
110
113
  findBySiren: () => Promise.reject(new Error("💣")),
111
114
  },
112
- FranceConnectApiRepository: {
113
- getFranceConnectUserInfo: () =>
114
- Promise.resolve(RogalDornFranceConnectUserInfo),
115
- },
116
115
  });
117
116
 
118
- const isDirigeant = await isOrganizationDirigeant(papillon_org_info, 1);
117
+ const certificationDirigeantResult = await processCertificationDirigeant(
118
+ papillon_org_info,
119
+ RogalDornFranceConnectUserInfo,
120
+ );
119
121
 
120
- assert.deepEqual(isDirigeant, {
122
+ assert.deepEqual(certificationDirigeantResult, {
121
123
  cause: "exact_match",
122
124
  details: {
123
125
  dirigeant: {
124
126
  ...IdentityVectorSchema.parse(RogalDornFranceConnectUserInfo),
125
127
  birthplace: null, // RNE adapter returns null for foreigners
126
128
  },
127
- score: 5,
129
+ matches: new Set([
130
+ "family_name",
131
+ "first_name",
132
+ "gender",
133
+ "birth_date",
134
+ "birth_country",
135
+ ]),
128
136
  identity: IdentityVectorSchema.parse(RogalDornFranceConnectUserInfo),
129
137
  source: "registre-national-entreprises.inpi.fr/api",
130
138
  },
@@ -133,7 +141,7 @@ describe("isOrganizationDirigeantFactory", () => {
133
141
  });
134
142
 
135
143
  it("should match Rogal Dorn among the executive of Papillon in Infogreffe", async () => {
136
- const isOrganizationDirigeant = isOrganizationDirigeantFactory({
144
+ const processCertificationDirigeant = processCertificationDirigeantFactory({
137
145
  ApiEntrepriseInfogreffeRepository: {
138
146
  findMandatairesSociauxBySiren: () =>
139
147
  Promise.resolve([UlysseToriMandataire, RogalDornMandataire]),
@@ -144,15 +152,14 @@ describe("isOrganizationDirigeantFactory", () => {
144
152
  InseeApiRepository: {
145
153
  findBySiren: () => Promise.reject(new Error("💣")),
146
154
  },
147
- FranceConnectApiRepository: {
148
- getFranceConnectUserInfo: () =>
149
- Promise.resolve(RogalDornFranceConnectUserInfo),
150
- },
151
155
  });
152
156
 
153
- const isDirigeant = await isOrganizationDirigeant(papillon_org_info, 1);
157
+ const certificationDirigeantResult = await processCertificationDirigeant(
158
+ papillon_org_info,
159
+ RogalDornFranceConnectUserInfo,
160
+ );
154
161
 
155
- assert.deepEqual(isDirigeant, {
162
+ assert.deepEqual(certificationDirigeantResult, {
156
163
  cause: "exact_match",
157
164
  details: {
158
165
  dirigeant: {
@@ -160,7 +167,13 @@ describe("isOrganizationDirigeantFactory", () => {
160
167
  birthplace: null, // API Entreprise adapter doesn't provide birthplace
161
168
  gender: null, // API Entreprise adapter doesn't provide gender
162
169
  },
163
- score: 5,
170
+ matches: new Set([
171
+ "family_name",
172
+ "first_name",
173
+ "gender",
174
+ "birth_date",
175
+ "birth_country",
176
+ ]),
164
177
  identity: IdentityVectorSchema.parse(RogalDornFranceConnectUserInfo),
165
178
  source:
166
179
  "entreprise.api.gouv.fr/v3/infogreffe/rcs/unites_legales/{siren}/mandataires_sociaux",
@@ -169,48 +182,33 @@ describe("isOrganizationDirigeantFactory", () => {
169
182
  });
170
183
  });
171
184
 
172
- it("❎ fail with no franceconnect user info", async () => {
173
- const isOrganizationDirigeant = isOrganizationDirigeantFactory({
185
+ it("❎ fail with no mandataires", async () => {
186
+ const processCertificationDirigeant = processCertificationDirigeantFactory({
174
187
  ApiEntrepriseInfogreffeRepository: {
175
188
  findMandatairesSociauxBySiren: () => Promise.reject(new Error("💣")),
176
189
  },
177
190
  RegistreNationalEntreprisesApiRepository: {
178
- findPouvoirsBySiren: () => Promise.reject(new Error("💣")),
191
+ findPouvoirsBySiren: () => Promise.resolve([]),
179
192
  },
180
193
  InseeApiRepository: {
181
194
  findBySiren: () => Promise.reject(new Error("💣")),
182
195
  },
183
- FranceConnectApiRepository: {
184
- getFranceConnectUserInfo: () => Promise.resolve(undefined),
185
- },
186
196
  });
187
197
 
188
- await assert.rejects(
189
- isOrganizationDirigeant(rogal_dorn_org_info, 1),
190
- new NotFoundError("FranceConnect UserInfo not found"),
198
+ const certificationDirigeantResult = await processCertificationDirigeant(
199
+ papillon_org_info,
200
+ LiElJonsonFranceConnectUserInfo,
191
201
  );
192
- });
193
202
 
194
- it("❎ fail with no mandataires", async () => {
195
- const isOrganizationDirigeant = isOrganizationDirigeantFactory({
196
- ApiEntrepriseInfogreffeRepository: {
197
- findMandatairesSociauxBySiren: () => Promise.reject(new Error("💣")),
198
- },
199
- RegistreNationalEntreprisesApiRepository: {
200
- findPouvoirsBySiren: () => Promise.resolve([]),
201
- },
202
- InseeApiRepository: {
203
- findBySiren: () => Promise.reject(new Error("💣")),
204
- },
205
- FranceConnectApiRepository: {
206
- getFranceConnectUserInfo: () =>
207
- Promise.resolve(LiElJonsonFranceConnectUserInfo),
203
+ assert.deepEqual(certificationDirigeantResult, {
204
+ cause: "no_candidates",
205
+ details: {
206
+ dirigeant: undefined,
207
+ identity: IdentityVectorSchema.parse(LiElJonsonFranceConnectUserInfo),
208
+ matches: new Set(),
209
+ source: "registre-national-entreprises.inpi.fr/api",
208
210
  },
211
+ ok: false,
209
212
  });
210
-
211
- await assert.rejects(
212
- isOrganizationDirigeant(papillon_org_info, 1),
213
- new InvalidCertificationError("No candidates found"),
214
- );
215
213
  });
216
214
  });