Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(identite): extract markDomainAsVerified function #983

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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