Skip to content

Commit

Permalink
refactor(identite): extract markDomainAsVerified function
Browse files Browse the repository at this point in the history
  • Loading branch information
douglasduteil committed Feb 18, 2025
1 parent 6ccd601 commit a28284b
Show file tree
Hide file tree
Showing 62 changed files with 985 additions and 355 deletions.
13 changes: 13 additions & 0 deletions .changeset/weak-insects-cry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"@gouvfr-lasuite/proconnect.core": minor
---

♻️ Prélevement de la fonction getEmailDomain

Permet l'extraction du domain d'un email.

```ts
import { getEmailDomain } from "@gouvfr-lasuite/proconnect.core/services/email";

getEmailDomain("[email protected]"); // darkangels.world
```
5 changes: 5 additions & 0 deletions .changeset/wicked-trainers-perform.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@gouvfr-lasuite/proconnect.identite": minor
---

♻️ Prélevement d'un partie du buisness proconnect identité
29 changes: 29 additions & 0 deletions packages/core/src/services/email/get-email-domain.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//

import { assert } from "chai";
import { getEmailDomain } from "./get-email-domain.js";

//

describe(getEmailDomain.name, () => {
const data = [
{
email: "[email protected]",
domain: "beta.gouv.fr",
},
{
email: "[email protected]",
domain: "notaires.fr",
},
{
email: "[email protected]",
domain: "subdomain.domain.org",
},
];

data.forEach(({ email, domain }) => {
it("should return email domain", () => {
assert.equal(getEmailDomain(email), domain);
});
});
});
20 changes: 20 additions & 0 deletions packages/core/src/services/email/get-email-domain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//

import { parse_host } from "tld-extract";

//

/**
* Get the domain of an email address
* @example
* getEmailDomain("[email protected]") // darkangels.world
* @param email - the email address
* @returns the domain of the email address
*/
export function getEmailDomain(email: string) {
const parts = email.split("@");
const host = parts[parts.length - 1];
const { sub, domain } = parse_host(host, { allowDotlessTLD: true });

return [sub, domain].filter(Boolean).join(".");
}
3 changes: 2 additions & 1 deletion packages/core/src/services/email/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//

export * from "./isAFreeDomain.js";
export * from "./get-email-domain.js";
export * from "./is-a-free-domain.js";
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { expect } from "chai";
import { test } from "mocha";
import { isAFreeDomain } from "./isAFreeDomain.js";
import { isAFreeDomain } from "./is-a-free-domain.js";

//

Expand Down
9 changes: 8 additions & 1 deletion packages/core/types/tld-extract.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
//

declare module "tld-extract" {
function parse_host(domain: string, { allowDotlessTLD: boolean }): boolean;
function parse_host(
domain: string,
{ allowDotlessTLD: boolean },
): {
tld: string;
domain: string;
sub: string;
};
}
3 changes: 2 additions & 1 deletion packages/identite/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"#src/*": {
"types": "./dist/*/index.d.ts",
"default": "./dist/*/index.js"
}
},
"#testing": "./testing/index.ts"
},
"exports": {
"./*": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { OrganizationInfo } from "@gouvfr-lasuite/proconnect.identite/types";
import type { OrganizationInfo } from "#src/types";
import {
type FindBySirenHandler,
type FindBySiretHandler,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//

export * from "./get-organization-info.js";
export * from "./upsert.js";
export * from "./mark-domain-as-verified.js";
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import {
type AddDomainHandler,
type FindEmailDomainsByOrganizationIdHandler,
} from "#src/repositories/email-domain";
import type {
FindByIdHandler,
GetUsersByOrganizationHandler,
} from "#src/repositories/organization";
import type { UpdateUserOrganizationLinkHandler } from "#src/repositories/user";
import type {
BaseUserOrganizationLink,
EmailDomain,
Organization,
User,
UserOrganizationLink,
} from "#src/types";
import * as chai from "chai";
import chaiAsPromised from "chai-as-promised";
import { mock } from "node:test";
import { markDomainAsVerifiedFactory } from "./mark-domain-as-verified.js";
//

chai.use(chaiAsPromised);
const assert = chai.assert;

describe(markDomainAsVerifiedFactory.name, () => {
it("should update organization members", async () => {
const addDomain = mock.fn<AddDomainHandler>(() =>
Promise.resolve({} as any),
);

const updateUserOrganizationLink =
mock.fn<UpdateUserOrganizationLinkHandler>(() =>
Promise.resolve({} as any),
);
const markDomainAsVerified = markDomainAsVerifiedFactory({
addDomain,
findEmailDomainsByOrganizationId:
mock.fn<FindEmailDomainsByOrganizationIdHandler>(),
findOrganizationById: mock.fn<FindByIdHandler>(() =>
Promise.resolve({ id: 42 } as Organization),
),
getUsers: mock.fn<GetUsersByOrganizationHandler>(() =>
Promise.resolve([
{
id: 42,
email: "[email protected]",
verification_type: null,
} as User & BaseUserOrganizationLink,
]),
),
updateUserOrganizationLink,
});

await markDomainAsVerified({
domain: "darkangels.world",
domain_verification_type: "verified",
organization_id: 42,
});

assert.deepEqual(updateUserOrganizationLink.mock.callCount(), 1);
{
const [call] = updateUserOrganizationLink.mock.calls;
assert.deepEqual(call.arguments, [
42,
42,
{ verification_type: "domain" },
]);
}

assert.deepEqual(addDomain.mock.callCount(), 1);
{
const [call] = addDomain.mock.calls;
assert.deepEqual(call.arguments, [
{
domain: "darkangels.world",
organization_id: 42,
verification_type: "verified",
},
]);
}
});

it("should add domain if organization if missing", async () => {
const logs = [] as unknown[];
const updateUserOrganizationLink: UpdateUserOrganizationLinkHandler = (
...args
) => {
logs.push(args);
return Promise.resolve({} as UserOrganizationLink);
};
const markDomainAsVerified = markDomainAsVerifiedFactory({
addDomain: () => Promise.resolve({} as EmailDomain),
findEmailDomainsByOrganizationId: () => Promise.resolve([]),
findOrganizationById: () => Promise.resolve({ id: 42 } as Organization),
getUsers: () =>
Promise.resolve([
{
id: 42,
email: "[email protected]",
verification_type: null,
} as User & BaseUserOrganizationLink,
]),
updateUserOrganizationLink,
});

await markDomainAsVerified({
domain: "darkangels.world",
domain_verification_type: "verified",
organization_id: 42,
});

assert.deepEqual(logs, [[42, 42, { verification_type: "domain" }]]);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
//

import type { GetUsersByOrganizationHandler } from "#src/repositories/organization";
import type { UpdateUserOrganizationLinkHandler } from "#src/repositories/user";
import { getEmailDomain } from "@gouvfr-lasuite/proconnect.core/services/email";
import type {
AddDomainHandler,
FindEmailDomainsByOrganizationIdHandler,
} from "@gouvfr-lasuite/proconnect.identite/repositories/email-domain";
import type { FindByIdHandler } from "@gouvfr-lasuite/proconnect.identite/repositories/organization";
import type { EmailDomain } from "@gouvfr-lasuite/proconnect.identite/types";
import { InseeNotFoundError } from "@gouvfr-lasuite/proconnect.insee/errors";
import { isEmpty, some } from "lodash-es";

//

type FactoryDependencies = {
addDomain: AddDomainHandler;
findEmailDomainsByOrganizationId: FindEmailDomainsByOrganizationIdHandler;
findOrganizationById: FindByIdHandler;
getUsers: GetUsersByOrganizationHandler;
updateUserOrganizationLink: UpdateUserOrganizationLinkHandler;
};

export function markDomainAsVerifiedFactory({
addDomain,
findEmailDomainsByOrganizationId,
findOrganizationById,
getUsers,
updateUserOrganizationLink,
}: FactoryDependencies) {
return async function markDomainAsVerified({
organization_id,
domain,
domain_verification_type,
}: {
organization_id: number;
domain: string;
domain_verification_type: EmailDomain["verification_type"];
}) {
const organization = await findOrganizationById(organization_id);
if (isEmpty(organization)) {
throw new InseeNotFoundError();
}
const emailDomains =
await findEmailDomainsByOrganizationId(organization_id);

if (
!some(emailDomains, {
domain,
verification_type: domain_verification_type,
})
) {
await addDomain({
organization_id,
domain,
verification_type: domain_verification_type,
});
}
const usersInOrganization = await getUsers(organization_id);

await Promise.all(
usersInOrganization.map(
({ id, email, verification_type: link_verification_type }) => {
const userDomain = getEmailDomain(email);
if (
userDomain === domain &&
[
null,
"no_verification_means_available",
"no_verification_means_for_entreprise_unipersonnelle",
].includes(link_verification_type)
) {
return updateUserOrganizationLink(organization_id, id, {
verification_type: "domain",
});
}

return null;
},
),
);
};
}
35 changes: 35 additions & 0 deletions packages/identite/src/repositories/email-domain/add-domain.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//

import { emptyDatabase, migrate, pg } from "#testing";
import { expect } from "chai";
import { before, describe, it } from "mocha";
import { addDomainFactory } from "./add-domain.js";

//

const addDomain = addDomainFactory({ pg: pg as any });

describe(addDomainFactory.name, () => {
before(migrate);
beforeEach(emptyDatabase);

it("should add domain", async () => {
await pg.sql`
INSERT INTO organizations
(id, siret, created_at, updated_at)
VALUES
(1, '66204244933106', '4444-04-04', '4444-04-04')
;
`;

const emailDomain = await addDomain({
domain: "darkangels.world",
organization_id: 1,
verification_type: "verified",
});

expect(emailDomain.domain).to.equal("darkangels.world");
expect(emailDomain.verification_type).to.equal("verified");
expect(emailDomain.created_at).to.deep.equal(emailDomain.updated_at);
});
});
Loading

0 comments on commit a28284b

Please sign in to comment.