Skip to content
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
14 changes: 10 additions & 4 deletions e2e/features/moderations/accept_blocking_moderation.feature
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,29 @@ Fonctionnalité: Accepter une modération bloquante avec la barre d'outils
Et un faux serveur "identite.proconnect.gouv.fr"
Quand je navigue sur la page
Et je clique sur le bouton "ProConnect"

Alors je dois voir le titre de page "Liste des moderations"
Alors je vois "Liste des moderations"
Quand je vais à l'intérieur de la rangée nommée "Modération a traiter de Jean Bon pour 51935970700022"
Quand je vais à l'intérieur de la rangée nommée "Modération a traiter de Jean Bon pour 13002526500013"
Et je clique sur "➡️"
Et je dois voir le titre de page "Modération a traiter de Jean Bon pour 51935970700022"
Et je réinitialise le contexte

Et je dois voir le titre de page "Modération a traiter de Jean Bon pour 13002526500013"

Scénario: Le modérateur le valide avec la barre d'outils
Quand je clique sur "✅ Accepter"
Alors je vois "A propos de jeanbon@yopmail.com pour l'organisation Abracadabra, je valide :"
Alors je vois "A propos de jeanbon@yopmail.com pour l'organisation DINUM, je valide :"

Soit je vais à l'intérieur du dialogue nommé "la modale de validation"
Quand je clique sur "Terminer"
Et je réinitialise le contexte
Quand je clique sur "Annuler"

Et je vois "Cette modération a été marqué comme traitée le"
Et je vois "Validé par user@yopmail.com"

Quand je clique sur "Moderations"
Alors je vois "Liste des moderations"
Alors je ne vois pas "51935970700022"
Alors je ne vois pas "13002526500013"

Alors une notification mail est envoyée
51 changes: 51 additions & 0 deletions e2e/features/moderations/validate_similar_moderations.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#language: fr
Fonctionnalité: Validation automatique des modérations similaires

Contexte:
Soit une base de données nourrie au grain
Et un faux serveur "identite.proconnect.gouv.fr"
Quand je navigue sur la page
Et je clique sur le bouton "ProConnect"

Alors je dois voir le titre de page "Liste des moderations"
Alors je vois "Liste des moderations"
Quand je vais à l'intérieur de la rangée nommée "Modération a traiter de Jean Bon pour 51935970700022"
Et je clique sur "➡️"
Et je réinitialise le contexte

Et je dois voir le titre de page "Modération a traiter de Jean Bon pour 51935970700022"

Plan du Scénario: Validation automatique des modérations similaires avec domaine yopmail.com
Quand je clique sur "✅ Accepter"
Alors je vois "A propos de jeanbon@yopmail.com pour l'organisation Abracadabra, je valide :"

Soit je vais à l'intérieur du dialogue nommé "la modale de validation"
Quand je clique sur "<add_member>"
Quand je clique sur "J’autorise le domaine <add_domain>"
Quand je clique sur "Terminer"
Et je réinitialise le contexte
Quand je clique sur "Annuler"

Alors je vois "Cette modération a été marqué comme traitée le"
Et je vois "Validé par user@yopmail.com"

Quand je clique sur "Moderations"
Alors je dois voir le titre de page "Liste des moderations"
Et je clique sur "Voir les demandes traitées"

Et je ne vois pas "Jean Bon"
Et je ne vois pas "Jean Dré"

Quand je clique sur "Siret"
Et je tape "51935970700022"
Quand je vais à l'intérieur de la rangée nommée "Modération a traiter de Jean Dré pour 51935970700022"
Et je clique sur "✅"
Et je réinitialise le contexte

Alors je dois voir le titre de page "Modération a traiter de Jean Dré pour 51935970700022"
Alors je vois "Validation automatique - <cause>"

Exemples:
| add_member | add_domain | cause |
| EN TANT QU'INTERNE | yopmail.com en interne à l'organisation | domaine vérifié |
| EN TANT QU'EXTERNE | yopmail.com en externe à l'organisation | domaine externe vérifié |
16 changes: 16 additions & 0 deletions sources/infra/identite-proconnect/database/src/seed/insert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { insert_dinum } from "./organizations/dinum";
import { insert_sak } from "./organizations/sak";
import { insert_yes_we_hack } from "./organizations/yes_we_hack";
import { insert_jeanbon } from "./users/jeanbon";
import { insert_jeandre } from "./users/jeandre";
import { insert_mariebon } from "./users/mariebon";
import { insert_pierrebon } from "./users/pierrebon";
import { insert_raphael } from "./users/raphael";
Expand All @@ -36,6 +37,10 @@ export async function insert_database(db: IdentiteProconnect_PgDatabase) {
consola.verbose(
`🌱 INSERT user ${jean_bon.given_name} ${jean_bon.family_name}`,
);
const jean_dre = await insert_jeandre(db);
consola.verbose(
`🌱 INSERT user ${jean_dre.given_name} ${jean_dre.family_name}`,
);
const pierre_bon = await insert_pierrebon(db);
consola.verbose(
`🌱 INSERT user ${pierre_bon.given_name} ${pierre_bon.family_name}`,
Expand Down Expand Up @@ -111,6 +116,17 @@ export async function insert_database(db: IdentiteProconnect_PgDatabase) {
`🌱 INSERT ${jean_bon.given_name} wants to join ${abracadabra.cached_libelle}`,
);

await insert_moderation(db, {
created_at: new Date("2011-11-11 00:03:15").toISOString(),
organization_id: abracadabra.id,
type: "organization_join_block" as MCP_Moderation["type"],
user_id: jean_dre.id,
ticket_id: "session_789",
});
consola.verbose(
`🌱 INSERT ${jean_dre.given_name} wants to join ${abracadabra.cached_libelle}`,
);

await insert_moderation(db, {
organization_id: aldp.id,
type: "big_organization_join" as MCP_Moderation["type"],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//

import type { IdentiteProconnect_PgDatabase } from "../..";
import { schema } from "../../index";

//

export async function insert_jeandre(db: IdentiteProconnect_PgDatabase) {
const insert = await db
.insert(schema.users)
.values({
created_at: new Date("2024-01-15T10:30:00.000Z").toISOString(),
email: "jeandre@yopmail.com",
family_name: "Dré",
given_name: "Jean",
updated_at: new Date("2024-01-15T10:30:00.000Z").toISOString(),
})
.returning();

return insert.at(0)!;
}
106 changes: 56 additions & 50 deletions sources/moderations/api/src/:id/$procedures/validate.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,19 @@ test("GET /moderation/:id/$procedures/validate { add_domain: true, add_member: A
await pg.query.moderations.findFirst({
where: (table, { eq }) => eq(table.id, moderation_id),
}),
).toEqual({
id: moderation_id,
organization_id: unicorn_organization_id,
user_id: adora_pony_user_id,
type: "",
ticket_id: null,
moderated_at: "2222-01-02 00:00:00+00",
moderated_by: "Anais Tailhade <anais.tailhade@omage.gouv.fr>",
comment:
"7952428800000 anais.tailhade@omage.gouv.fr | Validé par anais.tailhade@omage.gouv.fr",
created_at: "2222-01-01 00:00:00+00",
});
).toMatchInlineSnapshot(`
{
"comment": "7952428800000 anais.tailhade@omage.gouv.fr | Validé par anais.tailhade@omage.gouv.fr | Raison : "[ProConnect] ✨ Modeation validée"",
"created_at": "2222-01-01 00:00:00+00",
"id": 1,
"moderated_at": "2222-01-02 00:00:00+00",
"moderated_by": "Anais Tailhade <anais.tailhade@omage.gouv.fr>",
"organization_id": 1,
"ticket_id": null,
"type": "",
"user_id": 1,
}
`);

expect(
await pg.query.users_organizations.findFirst({
Expand All @@ -79,19 +80,21 @@ test("GET /moderation/:id/$procedures/validate { add_domain: true, add_member: A
eq(table.user_id, adora_pony_user_id),
),
}),
).toEqual({
created_at: "2222-01-02 00:00:00+00",
has_been_greeted: false,
is_external: false,
needs_official_contact_email_verification: false,
official_contact_email_verification_sent_at: null,
official_contact_email_verification_token: null,
organization_id: unicorn_organization_id,
updated_at: "2222-01-02 00:00:00+00",
user_id: adora_pony_user_id,
verification_type: "domain",
verified_at: "2222-01-02 00:00:00+00",
});
).toMatchInlineSnapshot(`
{
"created_at": "2222-01-02 00:00:00+00",
"has_been_greeted": false,
"is_external": false,
"needs_official_contact_email_verification": false,
"official_contact_email_verification_sent_at": null,
"official_contact_email_verification_token": null,
"organization_id": 1,
"updated_at": "2222-01-02 00:00:00+00",
"user_id": 1,
"verification_type": "domain",
"verified_at": "2222-01-02 00:00:00+00",
}
`);
});

test("GET /moderation/:id/$procedures/validate { add_domain: false, add_member: AS_EXTERNAL }", async () => {
Expand Down Expand Up @@ -122,18 +125,19 @@ test("GET /moderation/:id/$procedures/validate { add_domain: false, add_member:
await pg.query.moderations.findFirst({
where: (table, { eq }) => eq(table.id, moderation_id),
}),
).toEqual({
id: moderation_id,
organization_id: unicorn_organization_id,
user_id: adora_pony_user_id,
type: "",
ticket_id: null,
moderated_at: "2222-01-02 00:00:00+00",
moderated_by: "Anais Tailhade <anais.tailhade@omage.gouv.fr>",
comment:
"7952428800000 anais.tailhade@omage.gouv.fr | Validé par anais.tailhade@omage.gouv.fr",
created_at: "2222-01-01 00:00:00+00",
});
).toMatchInlineSnapshot(`
{
"comment": "7952428800000 anais.tailhade@omage.gouv.fr | Validé par anais.tailhade@omage.gouv.fr | Raison : "[ProConnect] ✨ Modeation validée"",
"created_at": "2222-01-01 00:00:00+00",
"id": 1,
"moderated_at": "2222-01-02 00:00:00+00",
"moderated_by": "Anais Tailhade <anais.tailhade@omage.gouv.fr>",
"organization_id": 1,
"ticket_id": null,
"type": "",
"user_id": 1,
}
`);

expect(
await pg.query.users_organizations.findFirst({
Expand All @@ -143,19 +147,21 @@ test("GET /moderation/:id/$procedures/validate { add_domain: false, add_member:
eq(table.user_id, adora_pony_user_id),
),
}),
).toEqual({
created_at: "2222-01-02 00:00:00+00",
has_been_greeted: false,
is_external: true,
needs_official_contact_email_verification: false,
official_contact_email_verification_sent_at: null,
official_contact_email_verification_token: null,
organization_id: unicorn_organization_id,
updated_at: "2222-01-02 00:00:00+00",
user_id: adora_pony_user_id,
verification_type: "domain",
verified_at: "2222-01-02 00:00:00+00",
});
).toMatchInlineSnapshot(`
{
"created_at": "2222-01-02 00:00:00+00",
"has_been_greeted": false,
"is_external": true,
"needs_official_contact_email_verification": false,
"official_contact_email_verification_sent_at": null,
"official_contact_email_verification_token": null,
"organization_id": 1,
"updated_at": "2222-01-02 00:00:00+00",
"user_id": 1,
"verification_type": "domain",
"verified_at": "2222-01-02 00:00:00+00",
}
`);
});

async function given_moderation_42() {
Expand Down
15 changes: 14 additions & 1 deletion sources/moderations/api/src/:id/$procedures/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { MemberJoinOrganization } from "@~/moderations.lib/usecase/member_join_o
import {
GetModerationById,
GetModerationWithUser,
ValidateSimilarModerations,
} from "@~/moderations.repository";
import {
AddVerifiedDomain,
Expand All @@ -44,6 +45,8 @@ export default new Hono<App_Context>().patch(
const { id } = req.valid("param");
const { add_domain, add_member, send_notification, verification_type } =
req.valid("form");

//#region 💉 Inject dependencies
const add_verified_domain = AddVerifiedDomain({
get_organization_by_id: GetFicheOrganizationById({ pg: identite_pg }),
mark_domain_as_verified: MarkDomainAsVerified(identite_pg_client),
Expand All @@ -52,6 +55,9 @@ export default new Hono<App_Context>().patch(
const update_user_by_id_in_organization = UpdateUserByIdInOrganization({
pg: identite_pg,
});
const validate_similar_moderations =
ValidateSimilarModerations(identite_pg);
//#endregion

const [moderation_error, moderation] = await to(
get_moderation_with_user(id),
Expand Down Expand Up @@ -93,11 +99,18 @@ export default new Hono<App_Context>().patch(
consola.error(domain_error);
throw domain_error;
});

await validate_similar_moderations({
organization_id,
domain,
domain_verification_type:
add_member === "AS_INTERNAL" ? "verified" : "external",
userinfo,
});
}
//#endregion

//#region ✨ Member join organization

const is_external = match(add_member)
.with("AS_INTERNAL", () => false)
.with("AS_EXTERNAL", () => true)
Expand Down
7 changes: 6 additions & 1 deletion sources/moderations/lib/src/comment_message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const BUILTIN_COMMENT = z.object({
created_by: z.string().email(),
});
const VALIDATED_COMMENT = BUILTIN_COMMENT.extend({
reason: z.string().optional(),
type: z.literal("VALIDATED"),
});
const REJECTED_COMMENT = BUILTIN_COMMENT.extend({
Expand All @@ -28,7 +29,11 @@ export type Comment_Type =

export function comment_message(comment_type: Comment_Type) {
const comment_message = match(comment_type)
.with({ type: "VALIDATED" }, ({ created_by }) => `Validé par ${created_by}`)
.with({ type: "VALIDATED" }, ({ created_by, reason }) =>
[`Validé par ${created_by}`, reason ? `Raison : "${reason}"` : false]
.filter(Boolean)
.join(" | "),
)
.with(
{ type: "REPROCESSED" },
({ created_by }) => `Réouverte par ${created_by}`,
Expand Down
Loading