Skip to content

Commit

Permalink
add usecase CreateUserForAgency
Browse files Browse the repository at this point in the history
  • Loading branch information
clement-duport committed Sep 4, 2024
1 parent f756c26 commit c02089d
Show file tree
Hide file tree
Showing 7 changed files with 346 additions and 0 deletions.
11 changes: 11 additions & 0 deletions back/src/adapters/primary/routers/admin/createAdminRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,17 @@ export const createAdminRouter = (deps: AppDependencies): Router => {
),
);

sharedAdminRouter.addUserForAgency(
deps.inclusionConnectAuthMiddleware,
(req, res) =>
sendHttpResponse(req, res.status(201), () =>
deps.useCases.updateIcUserRoleForAgency.execute(
req.body,
req.payloads?.currentUser,
),
),
);

sharedAdminRouter.rejectIcUserForAgency(
deps.inclusionConnectAuthMiddleware,
(req, res) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import {
AgencyRight,
InclusionConnectedUser,
UserCreateParamsForAgency,
errors,
userCreateParamsForAgencySchema,
} from "shared";
import { createTransactionalUseCase } from "../../core/UseCase";
import { TimeGateway } from "../../core/time-gateway/ports/TimeGateway";
import { UuidGenerator } from "../../core/uuid-generator/ports/UuidGenerator";
import { throwIfNotAdmin } from "../helpers/throwIfIcUserNotBackofficeAdmin";

export type CreateUserForAgency = ReturnType<typeof makeCreateUserForAgency>;

export const makeCreateUserForAgency = createTransactionalUseCase<
UserCreateParamsForAgency,
void,
InclusionConnectedUser,
{ timeGateway: TimeGateway; uuidGenerator: UuidGenerator }
>(
{
name: "CreateUserForAgency",
inputSchema: userCreateParamsForAgencySchema,
},
async ({
inputParams: { agencyId, email, isNotifiedByEmail, roles, userId },
uow,
currentUser,
deps,
}) => {
throwIfNotAdmin(currentUser);
const agency = await uow.agencyRepository.getById(agencyId);
if (!agency) throw errors.agency.notFound({ agencyId });

if (agency.refersToAgencyId && roles.includes("validator"))
throw errors.agency.invalidRoleUpdateForAgencyWithRefersTo({
agencyId: agency.id,
role: "validator",
});

const existingUser = await uow.userRepository.getById(userId);

if (!existingUser) {
await uow.userRepository.save({
createdAt: deps.timeGateway.now().toISOString(),
email,
externalId: null,
firstName: "",
id: userId,
lastName: "",
});
}

const existingUserAgencyRights = existingUser?.agencyRights ?? [];
const agencyRight: AgencyRight = {
roles,
isNotifiedByEmail: isNotifiedByEmail,
agency,
};
await uow.userRepository.updateAgencyRights({
userId: userId,
agencyRights: [...existingUserAgencyRights, agencyRight],
});
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
import {
AgencyDtoBuilder,
InclusionConnectedUserBuilder,
errors,
expectPromiseToFailWithError,
expectToEqual,
} from "shared";
import { InMemoryAgencyRepository } from "../../agency/adapters/InMemoryAgencyRepository";
import { InMemoryUserRepository } from "../../core/authentication/inclusion-connect/adapters/InMemoryUserRepository";
import { CustomTimeGateway } from "../../core/time-gateway/adapters/CustomTimeGateway";
import { TimeGateway } from "../../core/time-gateway/ports/TimeGateway";
import { InMemoryUowPerformer } from "../../core/unit-of-work/adapters/InMemoryUowPerformer";
import { createInMemoryUow } from "../../core/unit-of-work/adapters/createInMemoryUow";
import { TestUuidGenerator } from "../../core/uuid-generator/adapters/UuidGeneratorImplementations";
import { UuidGenerator } from "../../core/uuid-generator/ports/UuidGenerator";
import {
CreateUserForAgency,
makeCreateUserForAgency,
} from "./CreateUserForAgency";

const backofficeAdminUser = new InclusionConnectedUserBuilder()
.withId("backoffice-admin-id")
.withIsAdmin(true)
.build();

const notAdminUser = new InclusionConnectedUserBuilder()
.withId("not-admin-id")
.withIsAdmin(false)
.build();

const agency = new AgencyDtoBuilder()
.withCounsellorEmails(["[email protected]"])
.build();

describe("CreateUserForAgency", () => {
let createUserForAgency: CreateUserForAgency;
let uowPerformer: InMemoryUowPerformer;
let userRepository: InMemoryUserRepository;
let agencyRepository: InMemoryAgencyRepository;
let timeGateway: TimeGateway;
let uuidGenerator: UuidGenerator;

beforeEach(() => {
const uow = createInMemoryUow();

userRepository = uow.userRepository;
agencyRepository = uow.agencyRepository;
uowPerformer = new InMemoryUowPerformer(uow);
userRepository.setInclusionConnectedUsers([
backofficeAdminUser,
notAdminUser,
]);
timeGateway = new CustomTimeGateway();
uuidGenerator = new TestUuidGenerator();
createUserForAgency = makeCreateUserForAgency({
uowPerformer,
deps: { timeGateway, uuidGenerator },
});
agencyRepository.setAgencies([agency]);
});

it("throws Forbidden if token payload is not backoffice token", async () => {
await expectPromiseToFailWithError(
createUserForAgency.execute(
{
userId: uuidGenerator.new(),
roles: ["counsellor"],
agencyId: "agency-1",
isNotifiedByEmail: true,
email: "[email protected]",
},
notAdminUser,
),
errors.user.forbidden({ userId: notAdminUser.id }),
);
});

it("throws not found if agency does not exist", async () => {
userRepository.setInclusionConnectedUsers([
backofficeAdminUser,
{
...notAdminUser,
agencyRights: [],
dashboards: {
agencies: {},
establishments: {},
},
},
]);

const agencyId = "Fake-Agency-Id";

await expectPromiseToFailWithError(
createUserForAgency.execute(
{
userId: uuidGenerator.new(),
roles: ["counsellor"],
agencyId,
isNotifiedByEmail: true,
email: "[email protected]",
},
backofficeAdminUser,
),
errors.agency.notFound({
agencyId,
}),
);
});

describe("Agency with refers to agency", () => {
const agencyWithRefersTo = new AgencyDtoBuilder()
.withId("agency-with-refers-to")
.withValidatorEmails([])
.withCounsellorEmails(["[email protected]"])
.withRefersToAgencyId(agency.id)
.build();

it("Throw when user have role validator", async () => {
agencyRepository.insert(agencyWithRefersTo);

expectPromiseToFailWithError(
createUserForAgency.execute(
{
userId: uuidGenerator.new(),
agencyId: agencyWithRefersTo.id,
roles: ["validator"],
isNotifiedByEmail: true,
email: "[email protected]",
},
backofficeAdminUser,
),
errors.agency.invalidRoleUpdateForAgencyWithRefersTo({
agencyId: agencyWithRefersTo.id,
role: "validator",
}),
);
});
});

it("create new user with its agency rights", async () => {
const newUserId = uuidGenerator.new();
userRepository.users = [];

await createUserForAgency.execute(
{
userId: newUserId,
agencyId: agency.id,
roles: ["counsellor"],
isNotifiedByEmail: false,
email: "[email protected]",
},
backofficeAdminUser,
);

expect(userRepository.users.length).toBe(1);
expectToEqual(await userRepository.getById(newUserId), {
createdAt: timeGateway.now().toISOString(),
externalId: null,
firstName: "",
id: newUserId,
email: "[email protected]",
lastName: "",
agencyRights: [
{ agency, isNotifiedByEmail: false, roles: ["counsellor"] },
],
dashboards: {
agencies: {},
establishments: {},
},
});
});

it("add agency rights to an existing user", async () => {
const anotherAgency = new AgencyDtoBuilder()
.withId("another-agency-id")
.build();
await agencyRepository.insert(anotherAgency);
const userId = uuidGenerator.new();
await userRepository.save({
id: userId,
email: "[email protected]",
firstName: "John",
lastName: "Doe",
externalId: null,
createdAt: timeGateway.now().toISOString(),
});
await userRepository.updateAgencyRights({
userId,
agencyRights: [
{
agency,
isNotifiedByEmail: true,
roles: ["validator"],
},
],
});

await createUserForAgency.execute(
{
userId,
agencyId: anotherAgency.id,
roles: ["counsellor"],
isNotifiedByEmail: false,
email: "[email protected]",
},
backofficeAdminUser,
);

expectToEqual(await userRepository.getById(userId), {
createdAt: timeGateway.now().toISOString(),
externalId: null,
firstName: "John",
id: userId,
email: "[email protected]",
lastName: "Doe",
agencyRights: [
{
agency,
isNotifiedByEmail: true,
roles: ["validator"],
},
{
agency: anotherAgency,
isNotifiedByEmail: false,
roles: ["counsellor"],
},
],
dashboards: {
agencies: {},
establishments: {},
},
});
});
});
4 changes: 4 additions & 0 deletions shared/src/admin/admin.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ export type UserUpdateParamsForAgency = {
email: Email | null;
};

export type UserCreateParamsForAgency = UserUpdateParamsForAgency & {
email: Email;
};

export type RejectIcUserRoleForAgencyParams = OmitFromExistingKeys<
UserUpdateParamsForAgency,
"roles" | "isNotifiedByEmail" | "email"
Expand Down
14 changes: 14 additions & 0 deletions shared/src/admin/admin.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,20 @@ export const adminRoutes = defineRoutes({
404: httpErrorSchema,
},
}),

addUserForAgency: defineRoute({
method: "post",
url: "/admin/inclusion-connected/users",
requestBodySchema: userUpdateParamsForAgencySchema,
...withAuthorizationHeaders,
responses: {
201: expressEmptyResponseBody,
400: httpErrorSchema,
401: httpErrorSchema,
404: httpErrorSchema,
},
}),

rejectIcUserForAgency: defineRoute({
method: "delete",
url: "/admin/inclusion-connected/users",
Expand Down
10 changes: 10 additions & 0 deletions shared/src/admin/admin.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
ManageConventionAdminForm,
ManageEstablishmentAdminForm,
RejectIcUserRoleForAgencyParams,
UserCreateParamsForAgency,
UserUpdateParamsForAgency,
WithUserFilters,
} from "./admin.dto";
Expand All @@ -25,6 +26,15 @@ export const userUpdateParamsForAgencySchema: z.Schema<UserUpdateParamsForAgency
email: emailSchema.or(z.null()),
});

export const userCreateParamsForAgencySchema: z.Schema<UserCreateParamsForAgency> =
z.object({
userId: userIdSchema,
agencyId: agencyIdSchema,
roles: z.array(agencyRoleSchema),
isNotifiedByEmail: z.boolean(),
email: emailSchema,
});

export const rejectIcUserRoleForAgencyParamsSchema: z.Schema<RejectIcUserRoleForAgencyParams> =
z.object({
agencyId: agencyIdSchema,
Expand Down
8 changes: 8 additions & 0 deletions shared/src/errors/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,14 @@ export const errors = {
new BadRequestError(
`Le role "${role}" n'est pas autorisé pour l'agence "${agencyId}" car cette agence n'a qu'une seul étape de validation.`,
),

invalidRoleUpdateForAgencyWithRefersTo: ({
agencyId,
role,
}: { agencyId: AgencyId; role: AgencyRole }) =>
new BadRequestError(
`Le role "${role}" n'est pas autorisé pour l'agence "${agencyId}" car cette agence est une structure d'accompagnement.`,
),
},
user: {
unauthorized: () => new UnauthorizedError(),
Expand Down

0 comments on commit c02089d

Please sign in to comment.