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

create contact establishment api v2 #326

Merged
merged 2 commits into from
Jun 22, 2023
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
6 changes: 4 additions & 2 deletions back/src/_testBuilders/ImmersionOfferEntityV2Builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { ImmersionOfferEntityV2 } from "../domain/immersionOffer/entities/Immers

const validImmersionOfferEntityV2: ImmersionOfferEntityV2 = {
romeCode: "B1805",
appellationLabel: "Styliste",
appellationCode: "19540",
score: 4.5,
createdAt: new Date("2022-05-15T12:00:00.000"),
};
Expand All @@ -23,13 +25,13 @@ export class ImmersionOfferEntityV2Builder
});
}

withAppellationCode(appellationCode: string | undefined) {
withAppellationCode(appellationCode: string) {
return new ImmersionOfferEntityV2Builder({
...this.entity,
appellationCode,
});
}
withAppellationLabel(appellationLabel: string | undefined) {
withAppellationLabel(appellationLabel: string) {
return new ImmersionOfferEntityV2Builder({
...this.entity,
appellationLabel,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ export type ContactEstablishmentByMailPublicV1Dto =
ContactInformationPublicV1<"EMAIL"> & {
message: string;
};
type ContactEstablishmentInPersonPublicV1Dto =

export type ContactEstablishmentInPersonPublicV1Dto =
ContactInformationPublicV1<"IN_PERSON">;
type ContactEstablishmentByPhonePublicV1Dto =

export type ContactEstablishmentByPhonePublicV1Dto =
ContactInformationPublicV1<"PHONE">;

export type ContactEstablishmentPublicV1Dto =
Expand All @@ -34,9 +36,13 @@ export const contactEstablishmentPublicV1ToDomain = (
if (contactRequest.contactMode === "EMAIL")
return {
...contactRequest,
romeCode: contactRequest.offer.romeCode,
potentialBeneficiaryPhone: "Numéro de téléphone non communiqué",
immersionObjective: null,
};

return contactRequest;
return {
...contactRequest,
romeCode: contactRequest.offer.romeCode,
};
};
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { z } from "zod";
import {
contactEstablishmentByPhoneSchema,
contactEstablishmentInPersonSchema,
emailSchema,
preferEmailContactSchema,
preferInPersonContactSchema,
preferPhoneContactSchema,
romeSchema,
siretSchema,
zTrimmedString,
} from "shared";
import {
ContactEstablishmentByMailPublicV1Dto,
ContactEstablishmentByPhonePublicV1Dto,
ContactEstablishmentInPersonPublicV1Dto,
ContactEstablishmentPublicV1Dto,
} from "./ContactEstablishmentPublicV1.dto";

Expand All @@ -28,11 +30,22 @@ const contactEstablishmentByMailSchemaV1: z.Schema<ContactEstablishmentByMailPub
message: zTrimmedString,
});

const contactEstablishmentByPhoneSchemaV1: z.Schema<ContactEstablishmentByPhonePublicV1Dto> =
z.object({
...commonFields,
contactMode: preferPhoneContactSchema,
});
const contactEstablishmentInPersonSchemaV1: z.Schema<ContactEstablishmentInPersonPublicV1Dto> =
z.object({
...commonFields,
contactMode: preferInPersonContactSchema,
});

const contactEstablishmentRequestSchemaV1: z.Schema<ContactEstablishmentPublicV1Dto> =
z.union([
contactEstablishmentByMailSchemaV1,
contactEstablishmentByPhoneSchema,
contactEstablishmentInPersonSchema,
contactEstablishmentByPhoneSchemaV1,
contactEstablishmentInPersonSchemaV1,
]);

export const contactEstablishmentPublicV1Schema: z.Schema<ContactEstablishmentPublicV1Dto> =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {
ContactEstablishmentRequestDto,
ContactMethod,
ImmersionObjective,
RomeCode,
SiretDto,
} from "shared";

type ContactInformationPublicV2<T extends ContactMethod> = {
romeCode: RomeCode;
siret: SiretDto;
potentialBeneficiaryFirstName: string;
potentialBeneficiaryLastName: string;
potentialBeneficiaryEmail: string;
contactMode: T;
};

type ContactEstablishmentByMailPublicV2Dto =
ContactInformationPublicV2<"EMAIL"> & {
message: string;
potentialBeneficiaryPhone: string;
immersionObjective: ImmersionObjective | null;
potentialBeneficiaryResumeLink?: string;
};

type ContactEstablishmentInPersonPublicV2Dto =
ContactInformationPublicV2<"IN_PERSON">;

type ContactEstablishmentByPhonePublicV2Dto =
ContactInformationPublicV2<"PHONE">;

export type ContactEstablishmentPublicV2Dto =
| ContactEstablishmentByPhonePublicV2Dto
| ContactEstablishmentInPersonPublicV2Dto
| ContactEstablishmentByMailPublicV2Dto;

export const contactEstablishmentPublicV2ToDomain = (
contactRequest: ContactEstablishmentPublicV2Dto,
): ContactEstablishmentRequestDto => contactRequest;
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { z } from "zod";
import { contactEstablishmentRequestSchema } from "shared";
import { ContactEstablishmentPublicV2Dto } from "./ContactEstablishmentPublicV2.dto";

export const contactEstablishmentPublicV2Schema: z.Schema<ContactEstablishmentPublicV2Dto> =
contactEstablishmentRequestSchema;
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { SuperTest, Test } from "supertest";
import { rueSaintHonoreDto } from "../../../../_testBuilders/addressDtos";
import { AppConfigBuilder } from "../../../../_testBuilders/AppConfigBuilder";
import { buildTestApp } from "../../../../_testBuilders/buildTestApp";
import { ContactEntityBuilder } from "../../../../_testBuilders/ContactEntityBuilder";
import { EstablishmentAggregateBuilder } from "../../../../_testBuilders/EstablishmentAggregateBuilder";
import { EstablishmentEntityBuilder } from "../../../../_testBuilders/EstablishmentEntityBuilder";
import { ImmersionOfferEntityV2Builder } from "../../../../_testBuilders/ImmersionOfferEntityV2Builder";
import {
InMemoryEstablishmentAggregateRepository,
TEST_POSITION,
} from "../../../secondary/immersionOffer/InMemoryEstablishmentAggregateRepository";
import { validAuthorizedApiKeyId } from "../../../secondary/InMemoryApiConsumerRepository";
import { ContactEstablishmentPublicV2Dto } from "../DtoAndSchemas/v2/input/ContactEstablishmentPublicV2.dto";

const contactEstablishment: ContactEstablishmentPublicV2Dto = {
contactMode: "EMAIL",
message: "Salut !",
siret: "11112222333344",
romeCode: "A0000",
potentialBeneficiaryEmail: "john.doe@mail.com",
potentialBeneficiaryFirstName: "John",
potentialBeneficiaryLastName: "Doe",
immersionObjective: "Confirmer un projet professionnel",
potentialBeneficiaryPhone: "0654334567",
};

describe("POST contact-establishment public V2 route", () => {
let request: SuperTest<Test>;
let establishmentAggregateRepository: InMemoryEstablishmentAggregateRepository;
let authToken: string;

beforeEach(async () => {
const config = new AppConfigBuilder()
.withRepositories("IN_MEMORY")
.withAuthorizedApiKeyIds([validAuthorizedApiKeyId])
.build();

const {
request: testAppRequest,
generateApiJwt,
inMemoryUow,
} = await buildTestApp(config);
request = testAppRequest;
authToken = generateApiJwt({
id: validAuthorizedApiKeyId,
});
establishmentAggregateRepository =
inMemoryUow.establishmentAggregateRepository;
});

it("refuses to contact if no api key is provided", async () => {
const response = await request.post(`/v2/contact-establishment`).send({});
expect(response.status).toBe(401);
});

it("returns 404 if siret not found", async () => {
const response = await request
.post(`/v2/contact-establishment`)
.set("Authorization", authToken)
.send(contactEstablishment);

expect(response.status).toBe(404);
});

it("contacts the establishment when everything goes right", async () => {
await establishmentAggregateRepository.insertEstablishmentAggregates([
new EstablishmentAggregateBuilder()
.withEstablishment(
new EstablishmentEntityBuilder()
.withSiret(contactEstablishment.siret)
.withPosition(TEST_POSITION)
.withNumberOfEmployeeRange("10-19")
.withAddress(rueSaintHonoreDto)
.build(),
)
.withContact(
new ContactEntityBuilder().withContactMethod("EMAIL").build(),
)
.withImmersionOffers([
new ImmersionOfferEntityV2Builder()
.withRomeCode(contactEstablishment.romeCode)
.build(),
])
.build(),
]);

const response = await request
.post(`/v2/contact-establishment`)
.set("Authorization", authToken)
.send(contactEstablishment);

expect(response.status).toBe(200);
expect(response.body).toBe("");
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Router } from "express";
import { contactEstablishmentRoute, pipeWithValue } from "shared";
import { createLogger } from "../../../../utils/logger";
import type { AppDependencies } from "../../config/createAppDependencies";
import {
ForbiddenError,
validateAndParseZodSchema,
} from "../../helpers/httpErrors";
import { sendHttpResponse } from "../../helpers/sendHttpResponse";
import { contactEstablishmentPublicV2ToDomain } from "../DtoAndSchemas/v2/input/ContactEstablishmentPublicV2.dto";
import { contactEstablishmentPublicV2Schema } from "../DtoAndSchemas/v2/input/ContactEstablishmentPublicV2.schema";

const logger = createLogger(__filename);

export const createApiKeyAuthRouterV2 = (deps: AppDependencies) => {
const publicV2Router = Router({ mergeParams: true });

publicV2Router.use(deps.apiKeyAuthMiddleware);

publicV2Router.route(`/${contactEstablishmentRoute}`).post(async (req, res) =>
sendHttpResponse(req, res, () => {
if (!req.apiConsumer?.isAuthorized) throw new ForbiddenError();
return pipeWithValue(
validateAndParseZodSchema(
contactEstablishmentPublicV2Schema,
req.body,
logger,
),
contactEstablishmentPublicV2ToDomain,
(contactRequest) =>
deps.useCases.contactEstablishment.execute(contactRequest),
);
}),
);

return publicV2Router;
};
Loading