From ba6f7dbd7faf31ebb00177cc6ed78f02fa1d387f Mon Sep 17 00:00:00 2001
From: Yoan VALLET <ext.sopra.yvallet@grandlyon.com>
Date: Wed, 23 Mar 2022 17:04:54 +0000
Subject: [PATCH] feat(personalOffers): create personal offers endpoints

---
 src/app.module.ts                             |   2 +
 src/auth/auth.service.spec.ts                 |   1 +
 .../dto/create-personal-offer.dto.ts          |  21 +++
 src/personal-offers/dto/personal-offer.dto.ts |  36 ++++
 .../personal-offers.controller.spec.ts        | 142 +++++++++++++++
 .../personal-offers.controller.ts             |  81 +++++++++
 src/personal-offers/personal-offers.module.ts |  18 ++
 .../personal-offers.service.spec.ts           | 111 ++++++++++++
 .../personal-offers.service.ts                |  41 +++++
 .../schemas/personal-offer.schema.ts          |  22 +++
 .../schemas/week.schema.ts                    |   2 +-
 src/structures/dto/structure.dto.ts           |   8 +-
 src/structures/schemas/structure.schema.ts    |   9 +-
 .../services/structure.service.spec.ts        |  46 ++++-
 src/structures/services/structures.service.ts |  60 ++++++-
 src/structures/structures.controller.spec.ts  |   2 +
 src/users/dto/create-user.dto.ts              |   5 +
 .../guards/isPersonalOfferOwner.guard.spec.ts | 104 +++++++++++
 .../guards/isPersonalOfferOwner.guard.ts      |  21 +++
 .../guards/isStructureOwner.guard.spec.ts     |   3 +-
 src/users/schemas/user.schema.ts              |   5 +
 src/users/services/users.service.spec.ts      |  44 ++++-
 src/users/services/users.service.ts           |  34 +++-
 test/mock/data/personalOffers.mock.data.ts    |  81 +++++++++
 test/mock/data/structures.mock.data.ts        | 168 ++++++++++++++++++
 test/mock/data/users.mock.data.ts             |  58 ++++++
 .../guards/isPersonalOfferOwner.mock.guard.ts |   2 +
 .../services/personalOffers.mock.service.ts   |  47 +++++
 test/mock/services/structures.mock.service.ts | 100 +++++++++++
 test/mock/services/user.mock.service.ts       |  40 +++++
 30 files changed, 1294 insertions(+), 20 deletions(-)
 create mode 100644 src/personal-offers/dto/create-personal-offer.dto.ts
 create mode 100644 src/personal-offers/dto/personal-offer.dto.ts
 create mode 100644 src/personal-offers/personal-offers.controller.spec.ts
 create mode 100644 src/personal-offers/personal-offers.controller.ts
 create mode 100644 src/personal-offers/personal-offers.module.ts
 create mode 100644 src/personal-offers/personal-offers.service.spec.ts
 create mode 100644 src/personal-offers/personal-offers.service.ts
 create mode 100644 src/personal-offers/schemas/personal-offer.schema.ts
 rename src/{structures => shared}/schemas/week.schema.ts (85%)
 create mode 100644 src/users/guards/isPersonalOfferOwner.guard.spec.ts
 create mode 100644 src/users/guards/isPersonalOfferOwner.guard.ts
 create mode 100644 test/mock/data/personalOffers.mock.data.ts
 create mode 100644 test/mock/data/structures.mock.data.ts
 create mode 100644 test/mock/data/users.mock.data.ts
 create mode 100644 test/mock/guards/isPersonalOfferOwner.mock.guard.ts
 create mode 100644 test/mock/services/personalOffers.mock.service.ts

diff --git a/src/app.module.ts b/src/app.module.ts
index 148e1d602..f8b0a733d 100644
--- a/src/app.module.ts
+++ b/src/app.module.ts
@@ -15,6 +15,7 @@ import { PagesModule } from './pages/pages.module';
 import { TempUserModule } from './temp-user/temp-user.module';
 import { NewsletterModule } from './newsletter/newsletter.module';
 import { ContactModule } from './contact/contact.module';
+import { PersonalOffersModule } from './personal-offers/personal-offers.module';
 @Module({
   imports: [
     ConfigurationModule,
@@ -34,6 +35,7 @@ import { ContactModule } from './contact/contact.module';
     TempUserModule,
     NewsletterModule,
     ContactModule,
+    PersonalOffersModule,
   ],
   controllers: [AppController],
 })
diff --git a/src/auth/auth.service.spec.ts b/src/auth/auth.service.spec.ts
index e8f8f9205..f67d73a15 100644
--- a/src/auth/auth.service.spec.ts
+++ b/src/auth/auth.service.spec.ts
@@ -44,6 +44,7 @@ describe('AuthService', () => {
         emailVerified: false,
         email: 'jacques.dupont@mii.com',
         role: 0,
+        personalOffers: [],
       };
       const loginDto: LoginDto = { email: 'jacques.dupont@mii.com', password: 'test1A!!' }; //NOSONAR
       expect(await service.validateUser(loginDto)).toStrictEqual(result);
diff --git a/src/personal-offers/dto/create-personal-offer.dto.ts b/src/personal-offers/dto/create-personal-offer.dto.ts
new file mode 100644
index 000000000..5cfc1d0e6
--- /dev/null
+++ b/src/personal-offers/dto/create-personal-offer.dto.ts
@@ -0,0 +1,21 @@
+import { ApiProperty } from '@nestjs/swagger';
+import { Type } from 'class-transformer';
+import { IsNotEmpty, ValidateNested } from 'class-validator';
+import { PersonalOfferDto } from './personal-offer.dto';
+
+export class CreatePersonalOfferDto {
+  @ApiProperty({
+    description: 'Id of the structure',
+    type: [String],
+  })
+  @IsNotEmpty()
+  structureId: string;
+
+  @ApiProperty({
+    description: 'Personal offer to create',
+    type: [String],
+  })
+  @ValidateNested({ each: true })
+  @Type(() => PersonalOfferDto)
+  personalOffer: PersonalOfferDto;
+}
diff --git a/src/personal-offers/dto/personal-offer.dto.ts b/src/personal-offers/dto/personal-offer.dto.ts
new file mode 100644
index 000000000..2db73fa51
--- /dev/null
+++ b/src/personal-offers/dto/personal-offer.dto.ts
@@ -0,0 +1,36 @@
+import { ApiProperty } from '@nestjs/swagger';
+
+export class PersonalOfferDto {
+  /** accompaniments **/
+  @ApiProperty({
+    description: 'List of procedures accompaniments',
+    type: [String],
+  })
+  proceduresAccompaniment: string[];
+  /** trainings **/
+  @ApiProperty({
+    description: 'List of base skills trainings',
+    type: [String],
+  })
+  baseSkills: string[];
+  @ApiProperty({
+    description: 'List of access right trainings',
+    type: [String],
+  })
+  accessRight: string[];
+  @ApiProperty({
+    description: 'List of digital, culture and security trainings',
+    type: [String],
+  })
+  digitalCultureSecurity: string[];
+  @ApiProperty({
+    description: 'List of social and profressional trainings',
+    type: [String],
+  })
+  socialAndProfessional: string[];
+  @ApiProperty({
+    description: 'List of parenting help trainings',
+    type: [String],
+  })
+  parentingHelp: string[];
+}
diff --git a/src/personal-offers/personal-offers.controller.spec.ts b/src/personal-offers/personal-offers.controller.spec.ts
new file mode 100644
index 000000000..9c7511d5a
--- /dev/null
+++ b/src/personal-offers/personal-offers.controller.spec.ts
@@ -0,0 +1,142 @@
+import { Test, TestingModule } from '@nestjs/testing';
+import { PersonalOffersController } from './personal-offers.controller';
+import { PersonalOffersService } from './personal-offers.service';
+import { PersonalOffersServiceMock } from '../../test/mock/services/personalOffers.mock.service';
+import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
+import { mockJwtAuthGuard } from '../../test/mock/guards/jwt-auth.mock.guard';
+import { IsPersonalOfferOwnerGuard } from '../users/guards/isPersonalOfferOwner.guard';
+import { mockIsPersonalOfferOwnerGuard } from '../../test/mock/guards/isPersonalOfferOwner.mock.guard';
+import {
+  createPersonalOffersDtoDataMock,
+  updatePersonalOffersDtoDataMock,
+  personalOffersDataMock,
+} from '../../test/mock/data/personalOffers.mock.data';
+import { UsersService } from '../users/services/users.service';
+import { UsersServiceMock } from '../../test/mock/services/user.mock.service';
+import { StructuresService } from '../structures/services/structures.service';
+import { StructuresServiceMock } from '../../test/mock/services/structures.mock.service';
+
+describe('PersonalOffersController', () => {
+  let controller: PersonalOffersController;
+
+  beforeEach(async () => {
+    const module: TestingModule = await Test.createTestingModule({
+      controllers: [PersonalOffersController],
+      providers: [
+        {
+          provide: PersonalOffersService,
+          useClass: PersonalOffersServiceMock,
+        },
+        {
+          provide: UsersService,
+          useClass: UsersServiceMock,
+        },
+        {
+          provide: StructuresService,
+          useClass: StructuresServiceMock,
+        },
+      ],
+    })
+      .overrideGuard(JwtAuthGuard)
+      .useValue(mockJwtAuthGuard)
+      .overrideGuard(IsPersonalOfferOwnerGuard)
+      .useValue(mockIsPersonalOfferOwnerGuard)
+      .compile();
+
+    controller = module.get<PersonalOffersController>(PersonalOffersController);
+  });
+
+  it('should be defined', () => {
+    expect(controller).toBeDefined();
+  });
+
+  describe('find personal offer', () => {
+    it('should get personal offer', async () => {
+      expect(await controller.find('1234ba0e2ab5775cfc01ed3e')).toBe(personalOffersDataMock[0]);
+    });
+    it('should get personal offer does not exist', async () => {
+      try {
+        await controller.find('abcd');
+        // Fail test if above expression doesn't throw anything.
+        expect(true).toBe(false);
+      } catch (e) {
+        expect(e.message).toBe('Personal offer does not exist');
+        expect(e.status).toBe(404);
+      }
+    });
+  });
+
+  describe('create personal offer', () => {
+    it('should create personal offer for existing user and structure', async () => {
+      const req = { user: { _id: '6036721022462b001334c4bb' } };
+      expect(await controller.create(req, createPersonalOffersDtoDataMock[0])).toEqual(personalOffersDataMock[0]);
+    });
+    it('should return personal offer already exist in the structure', async () => {
+      const req = { user: { _id: '6036721022462b001334c4bb' } };
+      try {
+        await controller.create(req, createPersonalOffersDtoDataMock[1]);
+        // Fail test if above expression doesn't throw anything.
+        expect(true).toBe(false);
+      } catch (e) {
+        expect(e.message).toBe('Personal offer already exist in the structure');
+        expect(e.status).toBe(400);
+      }
+    });
+    it('should return structure not found for the personal offer attachment', async () => {
+      const req = { user: { _id: '6036721022462b001334c4bb' } };
+      try {
+        await controller.create(req, createPersonalOffersDtoDataMock[2]);
+        // Fail test if above expression doesn't throw anything.
+        expect(true).toBe(false);
+      } catch (e) {
+        expect(e.message).toBe('Structure not found for the personal offer attachment');
+        expect(e.status).toBe(400);
+      }
+    });
+    it('should return user not found for the personal offer attachment', async () => {
+      const req = { user: { _id: 'unIdQuiExistePasTropTrop' } };
+      try {
+        await controller.create(req, createPersonalOffersDtoDataMock[0]);
+        // Fail test if above expression doesn't throw anything.
+        expect(true).toBe(false);
+      } catch (e) {
+        expect(e.message).toBe('User not found for the personal offer attachment');
+        expect(e.status).toBe(400);
+      }
+    });
+  });
+
+  describe('update personal offer', () => {
+    it('should get updated personal offer', async () => {
+      expect(await controller.update('2345ba0e2ab5775cfc01ed4d', updatePersonalOffersDtoDataMock[1])).toEqual(
+        personalOffersDataMock[1]
+      );
+    });
+    it('should get invalid personal offer id', async () => {
+      try {
+        await controller.update('abcd', updatePersonalOffersDtoDataMock[1]);
+        // Fail test if above expression doesn't throw anything.
+        expect(true).toBe(false);
+      } catch (e) {
+        expect(e.message).toBe('Invalid personal offer id for update');
+        expect(e.status).toBe(400);
+      }
+    });
+  });
+
+  describe('update personal offer', () => {
+    it('should get deleted personal offer', async () => {
+      expect(await controller.delete('2345ba0e2ab5775cfc01ed4d')).toEqual(personalOffersDataMock[1]);
+    });
+    it('should get invalid personal offer id', async () => {
+      try {
+        await controller.delete('abcd');
+        // Fail test if above expression doesn't throw anything.
+        expect(true).toBe(false);
+      } catch (e) {
+        expect(e.message).toBe('Invalid personal offer id for deletion');
+        expect(e.status).toBe(400);
+      }
+    });
+  });
+});
diff --git a/src/personal-offers/personal-offers.controller.ts b/src/personal-offers/personal-offers.controller.ts
new file mode 100644
index 000000000..f2c72d4f0
--- /dev/null
+++ b/src/personal-offers/personal-offers.controller.ts
@@ -0,0 +1,81 @@
+import { Body, Controller, Delete, Get, Param, Post, Put, Request, UseGuards } from '@nestjs/common';
+import { ApiBody, ApiParam } from '@nestjs/swagger';
+import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
+import { StructuresService } from '../structures/services/structures.service';
+import { IsPersonalOfferOwnerGuard } from '../users/guards/isPersonalOfferOwner.guard';
+import { IUser } from '../users/interfaces/user.interface';
+import { UsersService } from '../users/services/users.service';
+import { CreatePersonalOfferDto } from './dto/create-personal-offer.dto';
+import { PersonalOfferDto } from './dto/personal-offer.dto';
+import { PersonalOffersService } from './personal-offers.service';
+import { PersonalOfferDocument } from './schemas/personal-offer.schema';
+
+@Controller('personal-offers')
+export class PersonalOffersController {
+  constructor(
+    private personalOffersService: PersonalOffersService,
+    private structuresService: StructuresService,
+    private usersService: UsersService
+  ) {}
+
+  /**
+   * Return personal offer of given id.
+   * @param id
+   * @returns {PersonalOffer} Personal offer
+   */
+  @Get(':id')
+  @ApiParam({ name: 'id', type: String, required: true })
+  public async find(@Param('id') id: string) {
+    return this.personalOffersService.findOne(id);
+  }
+
+  /**
+   * Create and return a personal offer.
+   * @param createPersonalOfferDto
+   * @returns {PersonalOffer} created personal offer
+   */
+  @Post()
+  @ApiBody({ type: CreatePersonalOfferDto, required: true })
+  @UseGuards(JwtAuthGuard)
+  public async create(
+    @Request() req,
+    @Body() createPersonalOfferDto: CreatePersonalOfferDto
+  ): Promise<PersonalOfferDocument> {
+    const user: IUser = req.user;
+    const personalOfferDocument: PersonalOfferDocument = await this.personalOffersService.create(
+      createPersonalOfferDto
+    );
+    await this.structuresService.addPersonalOffer(createPersonalOfferDto.structureId, personalOfferDocument);
+    await this.usersService.addPersonalOffer(user._id, personalOfferDocument);
+    return personalOfferDocument;
+  }
+
+  /**
+   * Update and return a personal offer.
+   * @param id - id of the personal offer to update
+   * @param updatePersonalOfferDto - personal offer data to update
+   * @returns {PersonalOffer} Updated personal offer
+   */
+  @Put(':id')
+  @ApiParam({ name: 'id', type: String, required: true })
+  @ApiBody({ type: PersonalOfferDto, required: true })
+  @UseGuards(JwtAuthGuard, IsPersonalOfferOwnerGuard)
+  public async update(
+    @Param('id') id: string,
+    @Body() updatePersonalOfferDto: PersonalOfferDto
+  ): Promise<PersonalOfferDocument> {
+    return this.personalOffersService.update(id, updatePersonalOfferDto);
+  }
+
+  /**
+   * Delete and return a personal offer.
+   * @param id - id of the personal offer to delete
+   * @returns {PersonalOffer} Deleted personal offer
+   */
+  @Delete(':id')
+  @ApiParam({ name: 'id', type: String, required: true })
+  @UseGuards(JwtAuthGuard, IsPersonalOfferOwnerGuard)
+  public async delete(@Param('id') id: string): Promise<PersonalOfferDocument> {
+    return this.personalOffersService.delete(id);
+  }
+}
diff --git a/src/personal-offers/personal-offers.module.ts b/src/personal-offers/personal-offers.module.ts
new file mode 100644
index 000000000..d4b634fd1
--- /dev/null
+++ b/src/personal-offers/personal-offers.module.ts
@@ -0,0 +1,18 @@
+import { Module } from '@nestjs/common';
+import { MongooseModule } from '@nestjs/mongoose';
+import { StructuresModule } from '../structures/structures.module';
+import { UsersModule } from '../users/users.module';
+import { PersonalOffersController } from './personal-offers.controller';
+import { PersonalOffersService } from './personal-offers.service';
+import { PersonalOffer, PersonalOfferSchema } from './schemas/personal-offer.schema';
+
+@Module({
+  imports: [
+    MongooseModule.forFeature([{ name: PersonalOffer.name, schema: PersonalOfferSchema }]),
+    StructuresModule,
+    UsersModule,
+  ],
+  controllers: [PersonalOffersController],
+  providers: [PersonalOffersService],
+})
+export class PersonalOffersModule {}
diff --git a/src/personal-offers/personal-offers.service.spec.ts b/src/personal-offers/personal-offers.service.spec.ts
new file mode 100644
index 000000000..835635d59
--- /dev/null
+++ b/src/personal-offers/personal-offers.service.spec.ts
@@ -0,0 +1,111 @@
+import { getModelToken } from '@nestjs/mongoose';
+import { Test, TestingModule } from '@nestjs/testing';
+import {
+  createPersonalOffersDtoDataMock,
+  updatePersonalOffersDtoDataMock,
+  personalOffersDataMock,
+} from '../../test/mock/data/personalOffers.mock.data';
+import { PersonalOffersService } from './personal-offers.service';
+
+describe('PersonalOffersService', () => {
+  let service: PersonalOffersService;
+
+  const personalOfferModelMock = {
+    findById: jest.fn(),
+    create: jest.fn(),
+    findByIdAndUpdate: jest.fn(),
+    findByIdAndDelete: jest.fn(),
+    exec: jest.fn(),
+  };
+
+  beforeEach(async () => {
+    const module: TestingModule = await Test.createTestingModule({
+      providers: [
+        PersonalOffersService,
+        {
+          provide: getModelToken('PersonalOffer'),
+          useValue: personalOfferModelMock,
+        },
+      ],
+    }).compile();
+
+    service = module.get<PersonalOffersService>(PersonalOffersService);
+  });
+
+  afterEach(async () => {
+    jest.clearAllMocks();
+  });
+
+  it('should be defined', () => {
+    expect(service).toBeDefined();
+  });
+
+  describe('findOne', () => {
+    it('should return personal offer', async () => {
+      personalOfferModelMock.findById.mockReturnThis();
+      personalOfferModelMock.exec.mockResolvedValueOnce(personalOffersDataMock[0]);
+      expect(await service.findOne('1234ba0e2ab5775cfc01ed3e')).toEqual(personalOffersDataMock[0]);
+    });
+    it('should return exception if personal offer is not found for given id', async () => {
+      personalOfferModelMock.findById.mockReturnThis();
+      personalOfferModelMock.exec.mockResolvedValueOnce(null);
+      let error: any;
+      try {
+        await service.findOne('abcd');
+      } catch (e) {
+        error = e;
+      }
+      expect(error.message).toBe('Personal offer does not exist');
+      expect(error.status).toBe(404);
+    });
+  });
+
+  describe('create', () => {
+    it('should create personal offer', async () => {
+      personalOfferModelMock.create.mockResolvedValueOnce(personalOffersDataMock[0]);
+      expect(await service.create(createPersonalOffersDtoDataMock[0])).toEqual(personalOffersDataMock[0]);
+    });
+  });
+
+  describe('update', () => {
+    it('should update personal offer', async () => {
+      personalOfferModelMock.findByIdAndUpdate.mockReturnThis();
+      personalOfferModelMock.exec.mockResolvedValueOnce(personalOffersDataMock[1]);
+      expect(await service.update('2345ba0e2ab5775cfc01ed4d', updatePersonalOffersDtoDataMock[1])).toEqual(
+        personalOffersDataMock[1]
+      );
+    });
+    it('should return exception if personal offer is not found for given id', async () => {
+      personalOfferModelMock.findById.mockReturnThis();
+      personalOfferModelMock.exec.mockResolvedValueOnce(null);
+      let error: any;
+      try {
+        await service.update('abcd', updatePersonalOffersDtoDataMock[1]);
+      } catch (e) {
+        error = e;
+      }
+      expect(error.message).toBe('Invalid personal offer id for update');
+      expect(error.status).toBe(400);
+    });
+  });
+
+  describe('delete', () => {
+    it('should update personal offer', async () => {
+      personalOfferModelMock.findByIdAndDelete.mockReturnThis();
+      personalOfferModelMock.exec.mockResolvedValueOnce(personalOffersDataMock[0]);
+      expect(await service.delete('2345ba0e2ab5775cfc01ed4d')).toEqual(personalOffersDataMock[0]);
+    });
+    it('should return exception if personal offer is not found for given id', async () => {
+      personalOfferModelMock.findById.mockReturnThis();
+      personalOfferModelMock.exec.mockResolvedValueOnce(null);
+      let error: any;
+      try {
+        await service.delete('abcd');
+      } catch (e) {
+        error = e;
+      }
+      expect(error.message).toBe('Invalid personal offer id for deletion');
+      expect(error.status).toBe(400);
+    });
+  });
+});
diff --git a/src/personal-offers/personal-offers.service.ts b/src/personal-offers/personal-offers.service.ts
new file mode 100644
index 000000000..13d00a67e
--- /dev/null
+++ b/src/personal-offers/personal-offers.service.ts
@@ -0,0 +1,41 @@
+import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
+import { InjectModel } from '@nestjs/mongoose';
+import { Model } from 'mongoose';
+import { CreatePersonalOfferDto } from './dto/create-personal-offer.dto';
+import { PersonalOfferDto } from './dto/personal-offer.dto';
+import { PersonalOffer, PersonalOfferDocument } from './schemas/personal-offer.schema';
+
+@Injectable()
+export class PersonalOffersService {
+  constructor(@InjectModel(PersonalOffer.name) private personalOfferModel: Model<PersonalOfferDocument>) {}
+
+  public async findOne(id: string): Promise<PersonalOffer> {
+    const result: PersonalOfferDocument = await this.personalOfferModel.findById(id).exec();
+    if (!result) {
+      throw new HttpException('Personal offer does not exist', HttpStatus.NOT_FOUND);
+    }
+    return result;
+  }
+
+  public async create(createDto: CreatePersonalOfferDto): Promise<PersonalOfferDocument> {
+    return this.personalOfferModel.create(createDto.personalOffer);
+  }
+
+  public async update(id: string, updatePersonalOfferDto: PersonalOfferDto): Promise<PersonalOfferDocument> {
+    const result: PersonalOfferDocument = await this.personalOfferModel
+      .findByIdAndUpdate(id, updatePersonalOfferDto)
+      .exec();
+    if (!result) {
+      throw new HttpException('Invalid personal offer id for update', HttpStatus.BAD_REQUEST);
+    }
+    return result;
+  }
+
+  public async delete(id: string): Promise<PersonalOfferDocument> {
+    const result: PersonalOfferDocument = await this.personalOfferModel.findByIdAndDelete(id).exec();
+    if (!result) {
+      throw new HttpException('Invalid personal offer id for deletion', HttpStatus.BAD_REQUEST);
+    }
+    return result;
+  }
+}
diff --git a/src/personal-offers/schemas/personal-offer.schema.ts b/src/personal-offers/schemas/personal-offer.schema.ts
new file mode 100644
index 000000000..00d985e3f
--- /dev/null
+++ b/src/personal-offers/schemas/personal-offer.schema.ts
@@ -0,0 +1,22 @@
+import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
+import { Document } from 'mongoose';
+
+export type PersonalOfferDocument = PersonalOffer & Document;
+
+@Schema({ timestamps: true })
+export class PersonalOffer {
+  @Prop()
+  proceduresAccompaniment: string[];
+  @Prop()
+  baseSkills: string[];
+  @Prop()
+  accessRight: string[];
+  @Prop()
+  digitalCultureSecurity: string[];
+  @Prop()
+  socialAndProfessional: string[];
+  @Prop()
+  parentingHelp: string[];
+}
+
+export const PersonalOfferSchema = SchemaFactory.createForClass(PersonalOffer);
diff --git a/src/structures/schemas/week.schema.ts b/src/shared/schemas/week.schema.ts
similarity index 85%
rename from src/structures/schemas/week.schema.ts
rename to src/shared/schemas/week.schema.ts
index 53c9fed58..44570306b 100644
--- a/src/structures/schemas/week.schema.ts
+++ b/src/shared/schemas/week.schema.ts
@@ -1,6 +1,6 @@
 import { SchemaFactory } from '@nestjs/mongoose';
 import { Document } from 'mongoose';
-import { Day } from './day.schema';
+import { Day } from '../../structures/schemas/day.schema';
 
 export type WeekDocument = Week & Document;
 
diff --git a/src/structures/dto/structure.dto.ts b/src/structures/dto/structure.dto.ts
index 4299a0cbf..438ca5d52 100644
--- a/src/structures/dto/structure.dto.ts
+++ b/src/structures/dto/structure.dto.ts
@@ -1,7 +1,8 @@
 import { Type } from 'class-transformer';
-import { ArrayNotEmpty, IsNotEmpty, ValidateNested } from 'class-validator';
+import { ArrayNotEmpty, IsArray, IsNotEmpty, ValidateNested } from 'class-validator';
 import { Address } from '../schemas/address.schema';
-import { Week } from '../schemas/week.schema';
+import { Week } from '../../shared/schemas/week.schema';
+import { PersonalOfferDocument } from '../../personal-offers/schemas/personal-offer.schema';
 
 export class structureDto {
   numero: string;
@@ -70,4 +71,7 @@ export class structureDto {
   coord: number[];
   accountVerified: boolean;
   dataShareConsentDate: Date;
+
+  @IsArray()
+  personalOffers: PersonalOfferDocument[];
 }
diff --git a/src/structures/schemas/structure.schema.ts b/src/structures/schemas/structure.schema.ts
index f3bf40679..1118541d3 100644
--- a/src/structures/schemas/structure.schema.ts
+++ b/src/structures/schemas/structure.schema.ts
@@ -1,9 +1,10 @@
 import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
 import { Type } from 'class-transformer';
 import { ArrayNotEmpty, IsNotEmpty, ValidateNested } from 'class-validator';
-import { Document } from 'mongoose';
+import { Document, Types } from 'mongoose';
 import { Address } from './address.schema';
-import { Week } from './week.schema';
+import { Week } from '../../shared/schemas/week.schema';
+import { PersonalOfferDocument } from '../../personal-offers/schemas/personal-offer.schema';
 
 export type StructureDocument = Structure & Document;
 
@@ -52,6 +53,7 @@ export class Structure {
     this.deletedAt = data.deletedAt;
     this.accountVerified = data.accountVerified;
     this.dataShareConsentDate = data.dataShareConsentDate;
+    this.personalOffers = data.personalOffers;
   }
 
   @Prop()
@@ -191,6 +193,9 @@ export class Structure {
 
   @Prop()
   dataShareConsentDate: Date;
+
+  @Prop({ type: [{ type: Types.ObjectId, ref: 'PersonalOffer' }] })
+  personalOffers: PersonalOfferDocument[];
 }
 
 export const StructureSchema = SchemaFactory.createForClass(Structure);
diff --git a/src/structures/services/structure.service.spec.ts b/src/structures/services/structure.service.spec.ts
index ca6be1066..f20271c7f 100644
--- a/src/structures/services/structure.service.spec.ts
+++ b/src/structures/services/structure.service.spec.ts
@@ -15,6 +15,9 @@ import { StructuresSearchService } from './structures-search.service';
 import { StructuresService } from './structures.service';
 import { IUser } from '../../users/interfaces/user.interface';
 import * as bcrypt from 'bcrypt';
+import { personalOffersDataMock } from '../../../test/mock/data/personalOffers.mock.data';
+import { PersonalOfferDocument } from '../../personal-offers/schemas/personal-offer.schema';
+import { structuresDocumentDataMock } from '../../../test/mock/data/structures.mock.data';
 
 function hashPassword() {
   return bcrypt.hashSync(process.env.USER_PWD, process.env.SALT);
@@ -29,6 +32,7 @@ describe('StructuresService', () => {
     findOne: jest.fn(),
     exec: jest.fn(),
     find: jest.fn(),
+    populate: jest.fn(),
   };
 
   const structuresSearchServiceMock = {
@@ -152,6 +156,7 @@ describe('StructuresService', () => {
         linkedin: null,
         nbScanners: 1,
         otherDescription: null,
+        personalOffers: [],
       },
     ]),
   };
@@ -258,6 +263,7 @@ describe('StructuresService', () => {
   describe('should searchForStructures', () => {
     jest.setTimeout(30000);
     mockStructureModel.find.mockReturnThis();
+    mockStructureModel.populate.mockReturnThis();
     mockStructureModel.exec.mockResolvedValue([
       {
         _id: '6903ba0e2ab5775cfc01ed4d',
@@ -322,6 +328,7 @@ describe('StructuresService', () => {
         linkedin: null,
         nbScanners: 1,
         otherDescription: null,
+        personalOffers: [],
       },
     ]);
 
@@ -401,6 +408,7 @@ describe('StructuresService', () => {
         ],
         pendingStructuresLink: [],
         structureOutdatedMailSent: [],
+        personalOffers: [],
         name: 'Jacques',
         surname: 'Dupont',
         phone: '06 06 06 06 06',
@@ -428,7 +436,7 @@ describe('StructuresService', () => {
           updatedAt: 'Thu Jan 20 2022 10:06:19 GMT+0100 (heure normale d’Europe centrale)',
           dataShareConsentDate: new Date(),
         } as StructureDocument);
-      let res = await service.getAllDataConsentPendingStructures(user);
+      const res = await service.getAllDataConsentPendingStructures(user);
       expect(res.length).toBe(1);
     });
     it('should get no consent', async () => {
@@ -450,6 +458,7 @@ describe('StructuresService', () => {
         ],
         pendingStructuresLink: [],
         structureOutdatedMailSent: [],
+        personalOffers: [],
         name: 'Jacques',
         surname: 'Dupont',
         phone: '06 06 06 06 06',
@@ -478,7 +487,7 @@ describe('StructuresService', () => {
           updatedAt: 'Thu Jan 20 2022 10:06:19 GMT+0100 (heure normale d’Europe centrale)',
           dataShareConsentDate: new Date(),
         } as StructureDocument);
-      let res = await service.getAllDataConsentPendingStructures(user);
+      const res = await service.getAllDataConsentPendingStructures(user);
       expect(res.length).toBe(0);
     });
   });
@@ -489,4 +498,37 @@ describe('StructuresService', () => {
     res = service.reportStructureError(null, '');
     expect(res).toBeTruthy();
   });
+
+  describe('addPersonalOffer', () => {
+    const personalOfferDocumentMock: PersonalOfferDocument = personalOffersDataMock[0] as PersonalOfferDocument;
+    it('should add personal offer to the structure', async () => {
+      jest.spyOn(service, 'findOne').mockResolvedValue(structuresDocumentDataMock[0]);
+      const expectedResult = { ...structuresDocumentDataMock[0], personalOffers: [personalOfferDocumentMock] };
+      expect(await service.addPersonalOffer('6093ba0e2ab5775cfc01ed3e', personalOfferDocumentMock)).toEqual(
+        expectedResult
+      );
+    });
+    it('should return exception if structure is not found for given id', async () => {
+      jest.spyOn(service, 'findOne').mockResolvedValue(null);
+      try {
+        await service.addPersonalOffer('abcd', personalOfferDocumentMock);
+        // Fail test if above expression doesn't throw anything.
+        expect(true).toBe(false);
+      } catch (e) {
+        expect(e.message).toBe('Structure not found for the personal offer attachment');
+        expect(e.status).toBe(400);
+      }
+    });
+    it('should return exception if personal offer already exists in the structure', async () => {
+      jest.spyOn(service, 'findOne').mockResolvedValue(structuresDocumentDataMock[1]);
+      try {
+        await service.addPersonalOffer('6093ba0e2ab5775cfc01ed3e', personalOfferDocumentMock);
+        // Fail test if above expression doesn't throw anything.
+        expect(true).toBe(false);
+      } catch (e) {
+        expect(e.message).toBe('Personal offer already exist in the structure');
+        expect(e.status).toBe(400);
+      }
+    });
+  });
 });
diff --git a/src/structures/services/structures.service.ts b/src/structures/services/structures.service.ts
index 4b209af4e..cdc1bd8a4 100644
--- a/src/structures/services/structures.service.ts
+++ b/src/structures/services/structures.service.ts
@@ -23,6 +23,7 @@ import { CategoriesOthers } from '../../categories/schemas/categoriesOthers.sche
 import { UnclaimedStructureDto } from '../../admin/dto/unclaimed-structure-dto';
 import { depRegex } from '../common/regex';
 import { CategoriesFormationsService } from '../../categories/services/categories-formations.service';
+import { PersonalOfferDocument } from '../../personal-offers/schemas/personal-offer.schema';
 
 @Injectable()
 export class StructuresService {
@@ -72,6 +73,7 @@ export class StructuresService {
             _id: { $in: ids },
             $and: [...this.parseFilter(filters), { deletedAt: { $exists: false }, accountVerified: true }],
           })
+          .populate('personalOffers')
           .exec()
       ).sort((a, b) => ids.indexOf(a.id) - ids.indexOf(b.id));
     } else if (filters?.length > 0 && multipleFilters?.length > 0) {
@@ -82,6 +84,7 @@ export class StructuresService {
             $or: [...this.parseFilter(multipleFilters)],
             $and: [...this.parseFilter(filters), { deletedAt: { $exists: false }, accountVerified: true }],
           })
+          .populate('personalOffers')
           .exec()
       ).sort((a, b) => ids.indexOf(a.id) - ids.indexOf(b.id));
     } else if (filters?.length == 0 && multipleFilters?.length > 0) {
@@ -91,6 +94,7 @@ export class StructuresService {
             _id: { $in: ids },
             $or: [...this.parseFilter(multipleFilters), { deletedAt: { $exists: false }, accountVerified: true }],
           })
+          .populate('personalOffers')
           .exec()
       ).sort((a, b) => ids.indexOf(a.id) - ids.indexOf(b.id));
     } else {
@@ -100,6 +104,7 @@ export class StructuresService {
             _id: { $in: ids },
             $and: [{ deletedAt: { $exists: false }, accountVerified: true }],
           })
+          .populate('personalOffers')
           .exec()
       ).sort((a, b) => ids.indexOf(a.id) - ids.indexOf(b.id));
     }
@@ -120,6 +125,7 @@ export class StructuresService {
             address: postition.address,
             coord: postition.coord,
           })
+          .populate('personalOffers')
           .exec()
       );
     });
@@ -145,16 +151,19 @@ export class StructuresService {
             { $text: { $search: searchString }, deletedAt: { $exists: false }, accountVerified: true },
           ],
         })
+        .populate('personalOffers')
         .exec();
     } else if (filters) {
       return this.structureModel
         .find({ $and: [{ $and: this.parseFilter(filters), deletedAt: { $exists: false }, accountVerified: true }] })
+        .populate('personalOffers')
         .exec();
     } else {
       return this.structureModel
         .find({
           $and: [{ $or: [{ $text: { $search: searchString }, deletedAt: { $exists: false }, accountVerified: true }] }],
         })
+        .populate('personalOffers')
         .exec();
     }
   }
@@ -184,16 +193,23 @@ export class StructuresService {
           return this.getStructurePosition(structure).then((postition: StructureDocument) => {
             this.structureModel
               .findByIdAndUpdate(Types.ObjectId(structure._id), { address: postition.address, coord: postition.coord })
+              .populate('personalOffers')
               .exec();
           });
         }
       })
     );
-    return this.structureModel.find({ deletedAt: { $exists: false }, accountVerified: true }).exec();
+    return this.structureModel
+      .find({ deletedAt: { $exists: false }, accountVerified: true })
+      .populate('personalOffers')
+      .exec();
   }
 
   public async findAllUnclaimed(): Promise<UnclaimedStructureDto[]> {
-    const structures = await this.structureModel.find({ deletedAt: { $exists: false } }).exec();
+    const structures = await this.structureModel
+      .find({ deletedAt: { $exists: false } })
+      .populate('personalOffers')
+      .exec();
     const unclaimedStructures = [];
     await Promise.all(
       structures.map(async (structure: StructureDocument) => {
@@ -206,7 +222,10 @@ export class StructuresService {
   }
 
   public async populateES(): Promise<StructureDocument[]> {
-    const structures = await this.structureModel.find({ deletedAt: { $exists: false } }).exec();
+    const structures = await this.structureModel
+      .find({ deletedAt: { $exists: false } })
+      .populate('personalOffers')
+      .exec();
     await Promise.all(
       structures.map((structure: StructureDocument) => {
         this.structuresSearchService.indexStructure(structure);
@@ -220,7 +239,10 @@ export class StructuresService {
     accompagnementCategories: CategoriesAccompagnement[],
     otherCategories: CategoriesOthers[]
   ): Promise<StructureDocument[]> {
-    const structures = await this.structureModel.find({ deletedAt: { $exists: false } }).exec();
+    const structures = await this.structureModel
+      .find({ deletedAt: { $exists: false } })
+      .populate('personalOffers')
+      .exec();
     // Update structures coord and address before sending them
     await Promise.all(
       structures.map((structure: StructureDocument) => {
@@ -232,6 +254,7 @@ export class StructuresService {
                 address: postition.address,
                 coord: postition.coord,
               })
+              .populate('personalOffers')
               .exec();
           });
         }
@@ -241,6 +264,7 @@ export class StructuresService {
       await this.structureModel
         .find({ deletedAt: { $exists: false }, accountVerified: true })
         .select('-_id -accountVerified -otherDescription')
+        .populate('personalOffers')
         .exec()
     ).map((structure) => {
       // If structure has temp email, hide it
@@ -338,7 +362,7 @@ export class StructuresService {
   }
 
   public async findOne(idParam: string): Promise<StructureDocument> {
-    return await this.structureModel.findById(Types.ObjectId(idParam)).exec();
+    return this.structureModel.findById(Types.ObjectId(idParam)).populate('personalOffers').exec();
   }
   /**
    * Get structures positions and add marker corresponding to those positons on the map
@@ -692,4 +716,30 @@ export class StructuresService {
     );
     return data.filter((value) => value);
   }
+
+  /**
+   * Add the personal offer to the structure
+   * @param structureId string
+   * @param personalOfferDocument PersonalOfferDocument
+   * @returns {Structure} structurew with personal offer added
+   */
+  public async addPersonalOffer(
+    structureId: string,
+    personalOfferDocument: PersonalOfferDocument
+  ): Promise<StructureDocument> {
+    const structure: StructureDocument = await this.findOne(structureId);
+    if (!structure) {
+      throw new HttpException('Structure not found for the personal offer attachment', HttpStatus.BAD_REQUEST);
+    }
+    if (
+      structure.personalOffers &&
+      structure.personalOffers.findIndex((personalOffer) => personalOffer._id === personalOfferDocument._id) > -1
+    ) {
+      throw new HttpException('Personal offer already exist in the structure', HttpStatus.BAD_REQUEST);
+    } else {
+      structure.personalOffers.push(personalOfferDocument);
+    }
+    await structure.save();
+    return structure;
+  }
 }
diff --git a/src/structures/structures.controller.spec.ts b/src/structures/structures.controller.spec.ts
index 459a9dfaf..5cc943105 100644
--- a/src/structures/structures.controller.spec.ts
+++ b/src/structures/structures.controller.spec.ts
@@ -132,6 +132,7 @@ describe('AuthController', () => {
       pendingStructuresLink: null,
       structuresLink: null,
       structureOutdatedMailSent: null,
+      personalOffers: null,
       email: user.email,
       name: user.name,
       surname: user.surname,
@@ -208,6 +209,7 @@ describe('AuthController', () => {
       pendingStructuresLink: null,
       structuresLink: null,
       structureOutdatedMailSent: null,
+      personalOffers: null,
       email: user.email,
       name: user.name,
       surname: user.surname,
diff --git a/src/users/dto/create-user.dto.ts b/src/users/dto/create-user.dto.ts
index fd1b6680f..b2f6b6fbb 100644
--- a/src/users/dto/create-user.dto.ts
+++ b/src/users/dto/create-user.dto.ts
@@ -1,5 +1,6 @@
 import { ApiProperty } from '@nestjs/swagger';
 import { IsArray, IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator';
+import { PersonalOffer } from '../../personal-offers/schemas/personal-offer.schema';
 
 export class CreateUserDto {
   @ApiProperty({ type: String })
@@ -31,4 +32,8 @@ export class CreateUserDto {
   @IsArray()
   @IsOptional()
   structuresLink?: Array<string>;
+
+  @IsArray()
+  @IsOptional()
+  personalOffers?: PersonalOffer[];
 }
diff --git a/src/users/guards/isPersonalOfferOwner.guard.spec.ts b/src/users/guards/isPersonalOfferOwner.guard.spec.ts
new file mode 100644
index 000000000..8c0785048
--- /dev/null
+++ b/src/users/guards/isPersonalOfferOwner.guard.spec.ts
@@ -0,0 +1,104 @@
+import { Reflector } from '@nestjs/core';
+import { createMock } from '@golevelup/ts-jest';
+import { Test, TestingModule } from '@nestjs/testing';
+import { ExecutionContext } from '@nestjs/common';
+import { UserRole } from '../enum/user-role.enum';
+import { IsPersonalOfferOwnerGuard } from './isPersonalOfferOwner.guard';
+
+describe('isPersonalOfferOwner', () => {
+  let guard: IsPersonalOfferOwnerGuard;
+  let reflector: Reflector;
+
+  beforeEach(async () => {
+    const module: TestingModule = await Test.createTestingModule({
+      imports: [],
+      providers: [
+        IsPersonalOfferOwnerGuard,
+        {
+          provide: Reflector,
+          useValue: {
+            constructor: jest.fn(),
+            get: jest.fn(),
+          },
+        },
+      ],
+    }).compile();
+
+    guard = module.get<IsPersonalOfferOwnerGuard>(IsPersonalOfferOwnerGuard);
+    reflector = module.get<Reflector>(Reflector);
+  });
+
+  afterEach(async () => {
+    jest.clearAllMocks();
+  });
+
+  it('should be defined', () => {
+    expect(guard).toBeDefined();
+  });
+
+  it('should return true if structure is in user linked structures', async () => {
+    const context = createMock<ExecutionContext>({
+      getHandler: jest.fn(),
+      switchToHttp: jest.fn().mockReturnValueOnce({
+        getRequest: jest.fn().mockReturnValueOnce({
+          user: {
+            structuresLink: [],
+            structureOutdatedMailSent: [],
+            pendingStructuresLink: [],
+            personalOffers: ['6001a38516b08100062e4161'],
+            newEmail: null,
+            changeEmailToken: null,
+            role: 0,
+            resetPasswordToken: null,
+            validationToken: null,
+            emailVerified: true,
+            email: 'jp@test.com',
+            name: 'Jean-Paul',
+            surname: 'DESCHAMPS',
+            phone: '06 11 11 11 11',
+          },
+          params: {
+            id: '6001a38516b08100062e4161',
+          },
+        }),
+      }),
+    });
+    const result = await guard.canActivate(context);
+
+    expect(result).toBeTruthy();
+  });
+
+  it('should return false if structure is not user linked structures', async () => {
+    jest.spyOn(reflector, 'get').mockImplementation((a: any, b: any) => []);
+    const context = createMock<ExecutionContext>({
+      getHandler: jest.fn(),
+      switchToHttp: jest.fn().mockReturnValue({
+        getRequest: jest.fn().mockReturnValue({
+          user: {
+            structuresLink: [],
+            structureOutdatedMailSent: [],
+            pendingStructuresLink: [],
+            personalOffers: ['unIdQuiExistePasTropTrop'],
+            newEmail: null,
+            changeEmailToken: null,
+            role: UserRole.user,
+            resetPasswordToken: null,
+            validationToken: null,
+            emailVerified: true,
+            email: 'jp@test.com',
+            name: 'Jean-Paul',
+            surname: 'DESCHAMPS',
+            phone: '06 11 11 11 11',
+          },
+          params: {
+            id: '6001a38516b08100062e4161',
+          },
+        }),
+      }),
+    });
+    const result = await guard.canActivate(context);
+
+    expect(result).toBeFalsy();
+    expect(reflector.get).toBeCalled();
+  });
+});
diff --git a/src/users/guards/isPersonalOfferOwner.guard.ts b/src/users/guards/isPersonalOfferOwner.guard.ts
new file mode 100644
index 000000000..4d73a0377
--- /dev/null
+++ b/src/users/guards/isPersonalOfferOwner.guard.ts
@@ -0,0 +1,21 @@
+import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
+import { Reflector } from '@nestjs/core';
+import { User } from '../schemas/user.schema';
+import { RolesGuard } from './roles.guard';
+
+@Injectable()
+export class IsPersonalOfferOwnerGuard extends RolesGuard implements CanActivate {
+  constructor(protected readonly reflector: Reflector) {
+    super(reflector);
+  }
+
+  canActivate(context: ExecutionContext): boolean {
+    const req = context.switchToHttp().getRequest();
+    const user: User = req.user;
+    const idPersonalOffer = req.params.id;
+    if (user.personalOffers && user.personalOffers.includes(idPersonalOffer)) {
+      return true;
+    }
+    return super.canActivate(context);
+  }
+}
diff --git a/src/users/guards/isStructureOwner.guard.spec.ts b/src/users/guards/isStructureOwner.guard.spec.ts
index a92ddcf4f..2e1bca68f 100644
--- a/src/users/guards/isStructureOwner.guard.spec.ts
+++ b/src/users/guards/isStructureOwner.guard.spec.ts
@@ -4,7 +4,6 @@ import { Test, TestingModule } from '@nestjs/testing';
 import { ExecutionContext } from '@nestjs/common';
 import { UserRole } from '../enum/user-role.enum';
 import { IsStructureOwnerGuard } from './isStructureOwner.guard';
-import { Types } from 'mongoose';
 
 describe('isStrructureOwner', () => {
   let guard: IsStructureOwnerGuard;
@@ -46,6 +45,7 @@ describe('isStrructureOwner', () => {
             structuresLink: ['6001a38516b08100062e4161'],
             structureOutdatedMailSent: [],
             pendingStructuresLink: [],
+            personalOffers: [],
             newEmail: null,
             changeEmailToken: null,
             role: 0,
@@ -78,6 +78,7 @@ describe('isStrructureOwner', () => {
             structuresLink: ['unIdQuiExistePasTropTrop'],
             structureOutdatedMailSent: [],
             pendingStructuresLink: [],
+            personalOffers: [],
             newEmail: null,
             changeEmailToken: null,
             role: UserRole.user,
diff --git a/src/users/schemas/user.schema.ts b/src/users/schemas/user.schema.ts
index 775a93d39..496365f8a 100644
--- a/src/users/schemas/user.schema.ts
+++ b/src/users/schemas/user.schema.ts
@@ -1,8 +1,10 @@
 import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
 import { Types } from 'mongoose';
+import { PersonalOfferDocument } from '../../personal-offers/schemas/personal-offer.schema';
 import { Employer } from './employer.schema';
 import { Job } from './job.schema';
 import { UserRole } from '../enum/user-role.enum';
+
 @Schema()
 export class User {
   @Prop({ required: true })
@@ -47,6 +49,9 @@ export class User {
   @Prop({ default: null })
   structureOutdatedMailSent: Types.ObjectId[];
 
+  @Prop({ type: [{ type: Types.ObjectId, ref: 'PersonalOffer' }] })
+  personalOffers: PersonalOfferDocument[];
+
   @Prop({ type: Types.ObjectId, ref: 'Employer' })
   employer: Employer;
 
diff --git a/src/users/services/users.service.spec.ts b/src/users/services/users.service.spec.ts
index c6f0b08fd..2eaee5ce8 100644
--- a/src/users/services/users.service.spec.ts
+++ b/src/users/services/users.service.spec.ts
@@ -3,6 +3,10 @@ import { UsersService } from './users.service';
 import { getModelToken } from '@nestjs/mongoose';
 import { HttpException, HttpStatus } from '@nestjs/common';
 import * as bcrypt from 'bcrypt';
+
+import { personalOffersDataMock } from '../../../test/mock/data/personalOffers.mock.data';
+import { PersonalOfferDocument } from '../../personal-offers/schemas/personal-offer.schema';
+import { usersMockData } from '../../../test/mock/data/users.mock.data';
 import { LoginDto } from '../../auth/login-dto';
 import { ConfigurationModule } from '../../configuration/configuration.module';
 import { MailerModule } from '../../mailer/mailer.module';
@@ -10,7 +14,7 @@ import { EmailChangeDto } from '../dto/change-email.dto';
 import { CreateUserDto } from '../dto/create-user.dto';
 import { User } from '../schemas/user.schema';
 import { IUser } from '../interfaces/user.interface';
-import { Types } from 'mongoose';
+
 function hashPassword() {
   return bcrypt.hashSync(process.env.USER_PWD, process.env.SALT);
 }
@@ -52,6 +56,7 @@ describe('UsersService', () => {
         structuresLink: [],
         structureOutdatedMailSent: [],
         pendingStructuresLink: [],
+        personalOffers: [],
         name: 'Jacques',
         surname: 'Dupont',
         phone: '06 06 06 06 06',
@@ -121,6 +126,7 @@ describe('UsersService', () => {
         structuresLink: [],
         pendingStructuresLink: [],
         structureOutdatedMailSent: [],
+        personalOffers: [],
         name: 'Jacques',
         surname: 'Dupont',
         phone: '06 06 06 06 06',
@@ -193,6 +199,7 @@ describe('UsersService', () => {
         structuresLink: [],
         pendingStructuresLink: [],
         structureOutdatedMailSent: [],
+        personalOffers: [],
         name: 'Jacques',
         surname: 'Dupont',
         phone: '06 06 06 06 06',
@@ -237,6 +244,7 @@ describe('UsersService', () => {
         structuresLink: [],
         pendingStructuresLink: [],
         structureOutdatedMailSent: [],
+        personalOffers: [],
         name: 'Jacques',
         surname: 'Dupont',
         phone: '06 06 06 06 06',
@@ -343,6 +351,7 @@ describe('UsersService', () => {
         structuresLink: [],
         pendingStructuresLink: [],
         structureOutdatedMailSent: [],
+        personalOffers: [],
         name: 'Jacques',
         surname: 'Dupont',
         phone: '06 06 06 06 06',
@@ -363,4 +372,37 @@ describe('UsersService', () => {
     jest.spyOn(service, 'findAllUnVerified').mockImplementation(async (): Promise<IUser[]> => result);
     expect((await service.findAllUnVerified()).length).toBe(0);
   });
+
+  describe('addPersonalOffer', () => {
+    const personalOfferDocumentMock: PersonalOfferDocument = personalOffersDataMock[0] as PersonalOfferDocument;
+    it('should add personal offer to the user', async () => {
+      jest.spyOn(service, 'findById').mockResolvedValue(usersMockData[0]);
+      const expectedResult = { ...usersMockData[0], personalOffers: [personalOfferDocumentMock] };
+      expect(await service.addPersonalOffer('6036721022462b001334c4bb', personalOfferDocumentMock)).toEqual(
+        expectedResult
+      );
+    });
+    it('should return exception if user is not found for given id', async () => {
+      jest.spyOn(service, 'findById').mockResolvedValue(null);
+      try {
+        await service.addPersonalOffer('abcd', personalOfferDocumentMock);
+        // Fail test if above expression doesn't throw anything.
+        expect(true).toBe(false);
+      } catch (e) {
+        expect(e.message).toBe('User not found for the personal offer attachment');
+        expect(e.status).toBe(400);
+      }
+    });
+    it('should return exception if personal offer already exists in the user', async () => {
+      jest.spyOn(service, 'findById').mockResolvedValue(usersMockData[1]);
+      try {
+        await service.addPersonalOffer('6093ba0e2ab5775cfc01ed3e', personalOfferDocumentMock);
+        // Fail test if above expression doesn't throw anything.
+        expect(true).toBe(false);
+      } catch (e) {
+        expect(e.message).toBe('Personal offer already exist in the user');
+        expect(e.status).toBe(400);
+      }
+    });
+  });
 });
diff --git a/src/users/services/users.service.ts b/src/users/services/users.service.ts
index 60c61e764..518f13d88 100644
--- a/src/users/services/users.service.ts
+++ b/src/users/services/users.service.ts
@@ -14,9 +14,9 @@ import { PendingStructureDto } from '../../admin/dto/pending-structure.dto';
 import { OwnerDto } from '../dto/owner.dto';
 import { StructureDocument } from '../../structures/schemas/structure.schema';
 import { ConfigurationService } from '../../configuration/configuration.service';
-import { ProfileDto } from '../dto/profile.dto';
-import { Employer, EmployerDocument } from '../schemas/employer.schema';
+import { EmployerDocument } from '../schemas/employer.schema';
 import { JobDocument } from '../schemas/job.schema';
+import { PersonalOfferDocument } from '../../personal-offers/schemas/personal-offer.schema';
 
 @Injectable()
 export class UsersService {
@@ -54,7 +54,7 @@ export class UsersService {
     // Send verification email
     createUser = await this.verifyUserMail(createUser);
     createUser.save();
-    return await this.findOne(createUserDto.email);
+    return this.findOne(createUserDto.email);
   }
 
   /**
@@ -72,11 +72,11 @@ export class UsersService {
   }
 
   private async comparePassword(attempt: string, password: string): Promise<boolean> {
-    return await bcrypt.compare(attempt, password);
+    return bcrypt.compare(attempt, password);
   }
 
   private async hashPassword(password: string): Promise<string> {
-    return await bcrypt.hash(password, process.env.SALT);
+    return bcrypt.hash(password, process.env.SALT);
   }
 
   public async findOne(mail: string, passwordQuery?: boolean): Promise<IUser | undefined> {
@@ -585,7 +585,29 @@ export class UsersService {
   }
 
   /**
-   *
+   * Add the personal offer to the user
+   * @param userId string
+   * @param personalOfferDocument PersonalOfferDocument
+   * @returns {IUser} user with personal offer added
+   */
+  public async addPersonalOffer(userId: string, personalOfferDocument: PersonalOfferDocument): Promise<IUser> {
+    const user: IUser = await this.findById(userId);
+    if (!user) {
+      throw new HttpException('User not found for the personal offer attachment', HttpStatus.BAD_REQUEST);
+    }
+    if (
+      user.personalOffers &&
+      user.personalOffers.findIndex((personalOffer) => personalOffer._id === personalOfferDocument._id) > -1
+    ) {
+      throw new HttpException('Personal offer already exist in the user', HttpStatus.BAD_REQUEST);
+    } else {
+      user.personalOffers.push(personalOfferDocument);
+    }
+    await user.save();
+    return user;
+  }
+
+  /**
    * @param profile
    * @param userId
    */
diff --git a/test/mock/data/personalOffers.mock.data.ts b/test/mock/data/personalOffers.mock.data.ts
new file mode 100644
index 000000000..eefafa12c
--- /dev/null
+++ b/test/mock/data/personalOffers.mock.data.ts
@@ -0,0 +1,81 @@
+import { CreatePersonalOfferDto } from '../../../src/personal-offers/dto/create-personal-offer.dto';
+import { PersonalOfferDto } from '../../../src/personal-offers/dto/personal-offer.dto';
+import { PersonalOffer } from '../../../src/personal-offers/schemas/personal-offer.schema';
+
+export const personalOffersDtoDataMock: PersonalOfferDto[] = [
+  {
+    proceduresAccompaniment: [],
+    baseSkills: [],
+    accessRight: [],
+    digitalCultureSecurity: [],
+    socialAndProfessional: [],
+    parentingHelp: [],
+  },
+  {
+    proceduresAccompaniment: ['cpam', 'impots', 'carsat', 'poleEmploi'],
+    baseSkills: ['260', '1', '11', '38', '48', '74', '77'],
+    accessRight: ['84', '85', '86', '87', '88', '89', '93', '95'],
+    digitalCultureSecurity: ['2', '5', '9', '28', '34', '39', '42', '51', '52', '54', '65', '96', '97', '98'],
+    socialAndProfessional: ['6', '20', '66', '67', '68', '69', '124', '125', '127'],
+    parentingHelp: ['3', '22', '82', '94'],
+  },
+];
+
+export const createPersonalOffersDtoDataMock: CreatePersonalOfferDto[] = [
+  {
+    structureId: '6093ba0e2ab5775cfc01ed3e',
+    personalOffer: personalOffersDtoDataMock[0],
+  },
+  {
+    structureId: '6093ba0e2ab5775cfc01fffe',
+    personalOffer: personalOffersDtoDataMock[1],
+  },
+  {
+    structureId: '6093ba0e2ab5775cfc01fffe',
+    personalOffer: personalOffersDtoDataMock[0],
+  },
+];
+
+export const updatePersonalOffersDtoDataMock: PersonalOfferDto[] = [
+  {
+    proceduresAccompaniment: [],
+    baseSkills: [],
+    accessRight: [],
+    digitalCultureSecurity: [],
+    socialAndProfessional: [],
+    parentingHelp: [],
+  },
+  {
+    proceduresAccompaniment: ['cpam', 'impots', 'carsat', 'poleEmploi'],
+    baseSkills: ['260', '1', '11', '38', '48', '74', '77'],
+    accessRight: ['84', '85', '86', '87', '88', '89', '93', '95'],
+    digitalCultureSecurity: ['2', '5', '9', '28', '34', '39', '42', '51', '52', '54', '65', '96', '97', '98'],
+    socialAndProfessional: ['6', '20', '66', '67', '68', '69', '124', '125', '127'],
+    parentingHelp: ['3', '22', '82', '94'],
+  },
+];
+
+export const personalOffersDataMock: PersonalOffer[] = [
+  {
+    _id: '1234ba0e2ab5775cfc01ed3e',
+    proceduresAccompaniment: [],
+    baseSkills: [],
+    accessRight: [],
+    digitalCultureSecurity: [],
+    socialAndProfessional: [],
+    parentingHelp: [],
+    createdAt: 'Wed Mar 16 2022 14:29:11 GMT+0100 (heure normale d’Europe centrale)',
+    updatedAt: 'Wed Mar 16 2022 17:29:11 GMT+0100 (heure normale d’Europe centrale)',
+  } as PersonalOffer,
+  {
+    _id: '2345ba0e2ab5775cfc01ed4d',
+    proceduresAccompaniment: ['cpam', 'impots', 'carsat', 'poleEmploi'],
+    baseSkills: ['260', '1', '11', '38', '48', '74', '77'],
+    accessRight: ['84', '85', '86', '87', '88', '89', '93', '95'],
+    digitalCultureSecurity: ['2', '5', '9', '28', '34', '39', '42', '51', '52', '54', '65', '96', '97', '98'],
+    socialAndProfessional: ['6', '20', '66', '67', '68', '69', '124', '125', '127'],
+    parentingHelp: ['3', '22', '82', '94'],
+    createdAt: 'Wed Mar 16 2022 14:29:11 GMT+0100 (heure normale d’Europe centrale)',
+    updatedAt: 'Wed Mar 16 2022 17:29:11 GMT+0100 (heure normale d’Europe centrale)',
+  } as PersonalOffer,
+];
diff --git a/test/mock/data/structures.mock.data.ts b/test/mock/data/structures.mock.data.ts
new file mode 100644
index 000000000..07f1135ac
--- /dev/null
+++ b/test/mock/data/structures.mock.data.ts
@@ -0,0 +1,168 @@
+import { StructureDocument } from '../../../src/structures/schemas/structure.schema';
+
+export const structuresDocumentDataMock: StructureDocument[] = [
+  {
+    _id: '6093ba0e2ab5775cfc01ed3e',
+    coord: [4.8498155, 45.7514817],
+    equipmentsAndServices: ['wifiEnAccesLibre'],
+    digitalCultureSecurity: [],
+    parentingHelp: [],
+    socialAndProfessional: [],
+    accessRight: [],
+    baseSkills: [],
+    proceduresAccompaniment: [],
+    publicsAccompaniment: [],
+    publics: ['adultes'],
+    labelsQualifications: [],
+    accessModality: ['telephoneVisio'],
+    structureType: null,
+    structureName: 'a',
+    description: null,
+    lockdownActivity: null,
+    address: {
+      numero: null,
+      street: 'Rue Alphonse Daudet',
+      commune: 'Lyon 7ème Arrondissement',
+    },
+    contactMail: '',
+    contactPhone: '',
+    website: '',
+    facebook: null,
+    twitter: null,
+    instagram: null,
+    linkedin: null,
+    hours: {
+      monday: {
+        open: false,
+        time: [],
+      },
+      tuesday: {
+        open: false,
+        time: [],
+      },
+      wednesday: {
+        open: false,
+        time: [],
+      },
+      thursday: {
+        open: false,
+        time: [],
+      },
+      friday: {
+        open: false,
+        time: [],
+      },
+      saturday: {
+        open: false,
+        time: [],
+      },
+      sunday: {
+        open: false,
+        time: [],
+      },
+    },
+    pmrAccess: false,
+    exceptionalClosures: null,
+    otherDescription: null,
+    nbComputers: 1,
+    nbPrinters: 1,
+    nbTablets: 1,
+    nbNumericTerminal: 1,
+    nbScanners: 1,
+    freeWorkShop: false,
+    accountVerified: true,
+    personalOffers: [],
+    createdAt: '2021-05-06T09:42:38.000Z',
+    updatedAt: '2021-05-06T09:42:50.000Z',
+    __v: 0,
+    save: jest.fn(),
+  } as any,
+  {
+    _id: '6093ba0e2ab5775cfc01ed3e',
+    coord: [4.8498155, 45.7514817],
+    equipmentsAndServices: ['wifiEnAccesLibre'],
+    digitalCultureSecurity: [],
+    parentingHelp: [],
+    socialAndProfessional: [],
+    accessRight: [],
+    baseSkills: [],
+    proceduresAccompaniment: [],
+    publicsAccompaniment: [],
+    publics: ['adultes'],
+    labelsQualifications: [],
+    accessModality: ['telephoneVisio'],
+    structureType: null,
+    structureName: 'a',
+    description: null,
+    lockdownActivity: null,
+    address: {
+      numero: null,
+      street: 'Rue Alphonse Daudet',
+      commune: 'Lyon 7ème Arrondissement',
+    },
+    contactMail: '',
+    contactPhone: '',
+    website: '',
+    facebook: null,
+    twitter: null,
+    instagram: null,
+    linkedin: null,
+    hours: {
+      monday: {
+        open: false,
+        time: [],
+      },
+      tuesday: {
+        open: false,
+        time: [],
+      },
+      wednesday: {
+        open: false,
+        time: [],
+      },
+      thursday: {
+        open: false,
+        time: [],
+      },
+      friday: {
+        open: false,
+        time: [],
+      },
+      saturday: {
+        open: false,
+        time: [],
+      },
+      sunday: {
+        open: false,
+        time: [],
+      },
+    },
+    pmrAccess: false,
+    exceptionalClosures: null,
+    otherDescription: null,
+    nbComputers: 1,
+    nbPrinters: 1,
+    nbTablets: 1,
+    nbNumericTerminal: 1,
+    nbScanners: 1,
+    freeWorkShop: false,
+    accountVerified: true,
+    personalOffers: [
+      {
+        _id: '1234ba0e2ab5775cfc01ed3e',
+        proceduresAccompaniment: [],
+        baseSkills: [],
+        accessRight: [],
+        digitalCultureSecurity: [],
+        socialAndProfessional: [],
+        parentingHelp: [],
+        createdAt: 'Wed Mar 16 2022 14:29:11 GMT+0100 (heure normale d’Europe centrale)',
+        updatedAt: 'Wed Mar 16 2022 17:29:11 GMT+0100 (heure normale d’Europe centrale)',
+      },
+    ],
+    createdAt: '2021-05-06T09:42:38.000Z',
+    updatedAt: '2021-05-06T09:42:50.000Z',
+    __v: 0,
+    save: jest.fn(),
+  } as any,
+] as StructureDocument[];
diff --git a/test/mock/data/users.mock.data.ts b/test/mock/data/users.mock.data.ts
new file mode 100644
index 000000000..5e1285cd3
--- /dev/null
+++ b/test/mock/data/users.mock.data.ts
@@ -0,0 +1,58 @@
+import { IUser } from '../../../src/users/interfaces/user.interface';
+
+export const usersMockData: IUser[] = [
+  {
+    structureOutdatedMailSent: [],
+    pendingStructuresLink: ['6001a48e16b08100062e4180', '6093ba0e2ab5775cfc01fffe'],
+    structuresLink: ['6093ba0e2ab5775cfc01fffe'],
+    newEmail: null,
+    changeEmailToken: null,
+    role: 0,
+    resetPasswordToken: null,
+    validationToken:
+      'b2b6caca1d38ca26d203b5f12b0d925df2928fab8ee7ccf9bbe78802ffa625f5abce825783bc62d0b11be5a90132cf5045a9a7776f01694c63b60bf64b0f680f',
+    emailVerified: false,
+    _id: '6036721022462b001334c4bb',
+    email: 'a@a.com',
+    name: 'Xavier',
+    surname: 'NIEL',
+    phone: '06 11 11 11 11',
+    password: '$2a$12$vLQjJ9zAWyUwiXLeQDa6w.XzrlgPBhw.2GWrjog/yuEjIaZnQwmZu',
+    personalOffers: [],
+    __v: 1,
+    save: jest.fn(),
+  } as any,
+  {
+    structureOutdatedMailSent: [],
+    pendingStructuresLink: ['6001a48e16b08100062e4180', '6093ba0e2ab5775cfc01fffe'],
+    structuresLink: ['6093ba0e2ab5775cfc01fffe'],
+    newEmail: null,
+    changeEmailToken: null,
+    role: 0,
+    resetPasswordToken: null,
+    validationToken:
+      'b2b6caca1d38ca26d203b5f12b0d925df2928fab8ee7ccf9bbe78802ffa625f5abce825783bc62d0b11be5a90132cf5045a9a7776f01694c63b60bf64b0f680f',
+    emailVerified: false,
+    _id: '6036721022462b001334c4bb',
+    email: 'a@a.com',
+    name: 'Xavier',
+    surname: 'NIEL',
+    phone: '06 11 11 11 11',
+    password: '$2a$12$vLQjJ9zAWyUwiXLeQDa6w.XzrlgPBhw.2GWrjog/yuEjIaZnQwmZu',
+    personalOffers: [
+      {
+        _id: '1234ba0e2ab5775cfc01ed3e',
+        proceduresAccompaniment: [],
+        baseSkills: [],
+        accessRight: [],
+        digitalCultureSecurity: [],
+        socialAndProfessional: [],
+        parentingHelp: [],
+        createdAt: 'Wed Mar 16 2022 14:29:11 GMT+0100 (heure normale d’Europe centrale)',
+        updatedAt: 'Wed Mar 16 2022 17:29:11 GMT+0100 (heure normale d’Europe centrale)',
+      },
+    ],
+    __v: 1,
+    save: jest.fn(),
+  } as any,
+] as IUser[];
diff --git a/test/mock/guards/isPersonalOfferOwner.mock.guard.ts b/test/mock/guards/isPersonalOfferOwner.mock.guard.ts
new file mode 100644
index 000000000..be66dd9a5
--- /dev/null
+++ b/test/mock/guards/isPersonalOfferOwner.mock.guard.ts
@@ -0,0 +1,2 @@
+import { CanActivate } from '@nestjs/common';
+export const mockIsPersonalOfferOwnerGuard: CanActivate = { canActivate: jest.fn(() => true) };
diff --git a/test/mock/services/personalOffers.mock.service.ts b/test/mock/services/personalOffers.mock.service.ts
new file mode 100644
index 000000000..a9fd932a5
--- /dev/null
+++ b/test/mock/services/personalOffers.mock.service.ts
@@ -0,0 +1,47 @@
+import { HttpException, HttpStatus } from '@nestjs/common';
+import { CreatePersonalOfferDto } from '../../../src/personal-offers/dto/create-personal-offer.dto';
+import { PersonalOfferDto } from '../../../src/personal-offers/dto/personal-offer.dto';
+import { createPersonalOffersDtoDataMock, personalOffersDataMock } from '../data/personalOffers.mock.data';
+
+export class PersonalOffersServiceMock {
+  findOne(id: string) {
+    if (id === '1234ba0e2ab5775cfc01ed3e') {
+      return personalOffersDataMock[0];
+    }
+    if (id === '2345ba0e2ab5775cfc01ed4d') {
+      return personalOffersDataMock[1];
+    }
+    throw new HttpException('Personal offer does not exist', HttpStatus.NOT_FOUND);
+  }
+
+  create(createPersonalOfferDto: CreatePersonalOfferDto) {
+    if (createPersonalOfferDto === createPersonalOffersDtoDataMock[1]) {
+      return {
+        ...createPersonalOfferDto.personalOffer,
+        _id: 'unIdQuiExisteDeja',
+        createdAt: 'Wed Mar 16 2022 14:29:11 GMT+0100 (heure normale d’Europe centrale)',
+        updatedAt: 'Wed Mar 16 2022 17:29:11 GMT+0100 (heure normale d’Europe centrale)',
+      };
+    }
+    return {
+      ...createPersonalOfferDto.personalOffer,
+      _id: '1234ba0e2ab5775cfc01ed3e',
+      createdAt: 'Wed Mar 16 2022 14:29:11 GMT+0100 (heure normale d’Europe centrale)',
+      updatedAt: 'Wed Mar 16 2022 17:29:11 GMT+0100 (heure normale d’Europe centrale)',
+    };
+  }
+
+  update(id: string, updateDto: PersonalOfferDto) {
+    if (id === '2345ba0e2ab5775cfc01ed4d') {
+      return { ...personalOffersDataMock[1], ...updateDto };
+    }
+    throw new HttpException('Invalid personal offer id for update', HttpStatus.BAD_REQUEST);
+  }
+
+  delete(id: string) {
+    if (id === '2345ba0e2ab5775cfc01ed4d') {
+      return personalOffersDataMock[1];
+    }
+    throw new HttpException('Invalid personal offer id for deletion', HttpStatus.BAD_REQUEST);
+  }
+}
diff --git a/test/mock/services/structures.mock.service.ts b/test/mock/services/structures.mock.service.ts
index d758744af..8c6379a76 100644
--- a/test/mock/services/structures.mock.service.ts
+++ b/test/mock/services/structures.mock.service.ts
@@ -1,4 +1,6 @@
 import { HttpException, HttpStatus } from '@nestjs/common';
+import { PersonalOffer, PersonalOfferDocument } from '../../../src/personal-offers/schemas/personal-offer.schema';
+import { StructureDocument } from '../../../src/structures/schemas/structure.schema';
 
 export class StructuresServiceMock {
   findOne(id) {
@@ -73,6 +75,7 @@ export class StructuresServiceMock {
         nbScanners: 1,
         freeWorkShop: false,
         accountVerified: true,
+        personalOffers: [],
         createdAt: '2021-05-06T09:42:38.000Z',
         updatedAt: '2021-05-06T09:42:50.000Z',
         __v: 0,
@@ -198,6 +201,7 @@ export class StructuresServiceMock {
         linkedin: null,
         nbScanners: 1,
         otherDescription: null,
+        personalOffers: [],
       };
     }
 
@@ -324,6 +328,7 @@ export class StructuresServiceMock {
         linkedin: null,
         nbScanners: 1,
         otherDescription: null,
+        personalOffers: [],
       };
     }
 
@@ -402,6 +407,7 @@ export class StructuresServiceMock {
         nbScanners: 1,
         freeWorkShop: false,
         accountVerified: true,
+        personalOffers: [],
         createdAt: '2021-05-06T09:42:38.000Z',
         updatedAt: '2021-05-06T09:42:50.000Z',
         __v: 0,
@@ -476,6 +482,7 @@ export class StructuresServiceMock {
         nbScanners: 1,
         freeWorkShop: false,
         accountVerified: true,
+        personalOffers: [],
         createdAt: '2021-05-06T09:42:38.000Z',
         updatedAt: '2021-05-06T09:42:50.000Z',
         __v: 0,
@@ -555,6 +562,7 @@ export class StructuresServiceMock {
         nbScanners: 1,
         freeWorkShop: false,
         accountVerified: true,
+        personalOffers: [],
         createdAt: '2021-05-06T09:42:38.000Z',
         updatedAt: '2021-05-06T09:42:50.000Z',
         __v: 0,
@@ -629,6 +637,7 @@ export class StructuresServiceMock {
         nbScanners: 1,
         freeWorkShop: false,
         accountVerified: true,
+        personalOffers: [],
         createdAt: '2021-05-06T09:42:38.000Z',
         updatedAt: '2021-05-06T09:42:50.000Z',
         __v: 0,
@@ -708,6 +717,7 @@ export class StructuresServiceMock {
         nbScanners: 1,
         freeWorkShop: false,
         accountVerified: true,
+        personalOffers: [],
         createdAt: '2021-05-06T09:42:38.000Z',
         updatedAt: '2021-05-06T09:42:50.000Z',
         __v: 0,
@@ -782,6 +792,7 @@ export class StructuresServiceMock {
         nbScanners: 1,
         freeWorkShop: false,
         accountVerified: true,
+        personalOffers: [],
         createdAt: '2021-05-06T09:42:38.000Z',
         updatedAt: '2021-05-06T09:42:50.000Z',
         __v: 0,
@@ -860,6 +871,7 @@ export class StructuresServiceMock {
       nbScanners: 1,
       freeWorkShop: false,
       accountVerified: true,
+      personalOffers: [],
       createdAt: '2021-05-06T09:42:38.000Z',
       updatedAt: '2021-05-06T09:42:50.000Z',
       __v: 0,
@@ -869,4 +881,92 @@ export class StructuresServiceMock {
   async getAllUserCompletedStructures(users: any[]): Promise<any[]> {
     return [];
   }
+
+  async addPersonalOffer(
+    structureId: string,
+    personalOfferDocument: PersonalOfferDocument
+  ): Promise<StructureDocument> {
+    if (structureId === '6093ba0e2ab5775cfc01ed3e') {
+      const personalOffer: PersonalOffer = { ...personalOfferDocument } as PersonalOffer;
+      return {
+        _id: '6093ba0e2ab5775cfc01ed3e',
+        coord: [4.8498155, 45.7514817],
+        digitalCultureSecurity: [],
+        parentingHelp: [],
+        socialAndProfessional: [],
+        accessRight: [],
+        baseSkills: ['260', '1', '11', '38', '48', '74', '77'],
+        equipmentsAndServices: ['ordinateurs', 'imprimantes'],
+        proceduresAccompaniment: [],
+        publicsAccompaniment: [],
+        publics: ['adultes'],
+        labelsQualifications: [],
+        accessModality: ['telephoneVisio'],
+        structureType: null,
+        structureName: 'a',
+        description: null,
+        lockdownActivity: null,
+        address: {
+          numero: null,
+          street: 'Rue Alphonse Daudet',
+          commune: 'Lyon 7ème Arrondissement',
+        },
+        contactMail: '',
+        contactPhone: '',
+        website: '',
+        facebook: null,
+        twitter: null,
+        instagram: null,
+        linkedin: null,
+        hours: {
+          monday: {
+            open: false,
+            time: [],
+          },
+          tuesday: {
+            open: false,
+            time: [],
+          },
+          wednesday: {
+            open: false,
+            time: [],
+          },
+          thursday: {
+            open: false,
+            time: [],
+          },
+          friday: {
+            open: false,
+            time: [],
+          },
+          saturday: {
+            open: false,
+            time: [],
+          },
+          sunday: {
+            open: false,
+            time: [],
+          },
+        },
+        pmrAccess: false,
+        exceptionalClosures: null,
+        otherDescription: null,
+        nbComputers: 1,
+        nbPrinters: 1,
+        nbTablets: 1,
+        nbNumericTerminal: 1,
+        nbScanners: 1,
+        freeWorkShop: false,
+        accountVerified: true,
+        personalOffers: [personalOffer],
+        createdAt: '2021-05-06T09:42:38.000Z',
+        updatedAt: '2021-05-06T09:42:50.000Z',
+        __v: 0,
+      } as StructureDocument;
+    }
+    if (personalOfferDocument._id === 'unIdQuiExisteDeja') {
+      throw new HttpException('Personal offer already exist in the structure', HttpStatus.BAD_REQUEST);
+    }
+    throw new HttpException('Structure not found for the personal offer attachment', HttpStatus.BAD_REQUEST);
+  }
 }
diff --git a/test/mock/services/user.mock.service.ts b/test/mock/services/user.mock.service.ts
index 5b3fc7d57..e13d72346 100644
--- a/test/mock/services/user.mock.service.ts
+++ b/test/mock/services/user.mock.service.ts
@@ -1,6 +1,8 @@
 import { HttpException, HttpStatus } from '@nestjs/common';
 import { PendingStructureDto } from '../../../src/admin/dto/pending-structure.dto';
 import { LoginDto } from '../../../src/auth/login-dto';
+import { PersonalOffer, PersonalOfferDocument } from '../../../src/personal-offers/schemas/personal-offer.schema';
+import { IUser } from '../../../src/users/interfaces/user.interface';
 import { User } from '../../../src/users/schemas/user.schema';
 
 export class UsersServiceMock {
@@ -16,6 +18,7 @@ export class UsersServiceMock {
         role: 0,
         name: 'DUPONT',
         surname: 'Pauline',
+        personalOffers: [],
       };
     }
     if (mail === 'jacques.dupont@mii.com') {
@@ -28,6 +31,7 @@ export class UsersServiceMock {
           email: 'jacques.dupont@mii.com',
           password: '$2a$12$vLQjJ9zAWyUwiFLeQDa6w.XzrlgPBhw.2GWrjog/yuEjIaZnQwmZu',
           role: 0,
+          personalOffers: [],
         };
       }
       return {
@@ -37,6 +41,7 @@ export class UsersServiceMock {
         emailVerified: false,
         email: 'jacques.dupont@mii.com',
         role: 0,
+        personalOffers: [],
       };
     }
     return null;
@@ -93,6 +98,7 @@ export class UsersServiceMock {
         name: 'DUPONT',
         surname: 'Pauline',
         structuresLink: ['abcdefgh', '18sfqfq'],
+        personalOffers: [],
       };
     }
     throw new HttpException('Invalid user id', HttpStatus.BAD_REQUEST);
@@ -112,6 +118,7 @@ export class UsersServiceMock {
           name: 'DUPONT',
           surname: 'Pauline',
           structuresLink: ['abcdefgh', '18sfqfq', '6093ba0e2ab5775cfc01fffe'],
+          personalOffers: [],
         });
       });
     }
@@ -140,6 +147,7 @@ export class UsersServiceMock {
           surname: 'NIEL',
           phone: '06 11 11 11 11',
           password: '$2a$12$vLQjJ9zAWyUwiXLeQDa6w.XzrlgPBhw.2GWrjog/yuEjIaZnQwmZu',
+          personalOffers: [],
           __v: 1,
         },
       ];
@@ -166,6 +174,7 @@ export class UsersServiceMock {
         surname: 'NIEL',
         phone: '06 11 11 11 11',
         password: '$2a$12$vLQjJ9zAWyUwiXLeQDa6w.XzrlgPBhw.2GWrjog/yuEjIaZnQwmZu',
+        personalOffers: [],
         __v: 1,
       },
       {
@@ -184,6 +193,7 @@ export class UsersServiceMock {
         surname: 'hh',
         phone: '05 79 87 65 78',
         password: '$2a$12$vLQjJ9zAWyUwiXLeQDa6w.3KdcYb.cLqZzkX2rstcpc2QTgbJ0FlC',
+        personalOffers: [],
         __v: 3,
       },
     ];
@@ -200,4 +210,34 @@ export class UsersServiceMock {
   async findAllUnVerified(): Promise<User[]> {
     return await [];
   }
+
+  public async addPersonalOffer(userId: string, personalOfferDocument: PersonalOfferDocument): Promise<IUser> {
+    if (userId === '6036721022462b001334c4bb') {
+      const personalOffer: PersonalOffer = { ...personalOfferDocument } as PersonalOffer;
+      return {
+        structureOutdatedMailSent: [],
+        pendingStructuresLink: [],
+        structuresLink: [],
+        newEmail: null,
+        changeEmailToken: null,
+        role: 0,
+        resetPasswordToken: null,
+        validationToken:
+          'b2b6caca1d38ca26d203b5f12b0d925df2928fab8ee7ccf9bbe78802ffa625f5abce825783bc62d0b11be5a90132cf5045a9a7776f01694c63b60bf64b0f680f',
+        emailVerified: false,
+        _id: '6036721022462b001334c4bb',
+        email: 'a@a.com',
+        name: 'Xavier',
+        surname: 'NIEL',
+        phone: '06 11 11 11 11',
+        password: '$2a$12$vLQjJ9zAWyUwiXLeQDa6w.XzrlgPBhw.2GWrjog/yuEjIaZnQwmZu',
+        personalOffers: [personalOffer],
+        __v: 1,
+      } as IUser;
+    }
+    if (personalOfferDocument._id === 'unIdQuiExisteDeja') {
+      throw new HttpException('Personal offer already exist in the user', HttpStatus.BAD_REQUEST);
+    }
+    throw new HttpException('User not found for the personal offer attachment', HttpStatus.BAD_REQUEST);
+  }
 }
-- 
GitLab