diff --git a/docker-compose.yml b/docker-compose.yml index 3cfa17557f6da51eae4fb2dce5036a338d46975e..ff23e61fc8f9ba00a175ce3211043d0d01616c55 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -87,7 +87,7 @@ services: - db-ghost:/var/lib/mysql es01: - image: elasticsearch:7.6.1 + image: elasticsearch:7.16.2 restart: unless-stopped environment: node.name: es01 diff --git a/package-lock.json b/package-lock.json index c32dba743fa033561364730f0db3a6b29cb20137..298a88b87bfad2b2718fae75a3d1c5c8653415d4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4448,6 +4448,12 @@ "integrity": "sha512-59SgoZ3EXbkfSX7b63tsou/SDGzwUEK6MuB5sKqgVK1/XE0fxmpsOb9DQI8LXW3KfGnAjImCGhhEb7uPPAUVNA==", "dev": true }, + "@golevelup/ts-jest": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@golevelup/ts-jest/-/ts-jest-0.3.2.tgz", + "integrity": "sha512-hv+j/vau2oj5CuhY1CrTs48Qu+ZVXpm/56OzKcL2KVN8+yc0ZIMbvTIld1wbrT1RrvAuwfDmUM2s3aSy7veuwg==", + "dev": true + }, "@hutson/parse-repository-url": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz", diff --git a/package.json b/package.json index 7bcfb722c57763dc38d71e4e785d5e0630e1cbc5..f0e4364726efd5249b775edaf3301d778e37eb29 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ }, "devDependencies": { "@compodoc/compodoc": "^1.1.16", + "@golevelup/ts-jest": "^0.3.2", "@nestjs/cli": "^7.5.1", "@nestjs/schematics": "^7.1.3", "@nestjs/testing": "^7.5.1", diff --git a/src/admin/admin.controller.ts b/src/admin/admin.controller.ts index ad0a493f351f0a25a9b66e18930f632f3c9f4966..3fae73b8f3970e7786f21eba848bef4ca20db68a 100644 --- a/src/admin/admin.controller.ts +++ b/src/admin/admin.controller.ts @@ -183,6 +183,6 @@ export class AdminController { @Delete('newsletterSubscription/:email') @ApiParam({ name: 'email', type: String, required: true }) public async unsubscribeUserFromNewsletter(@Param() params): Promise<NewsletterSubscription> { - return this.newsletterService.deleteOneEmail(params.email); + return this.newsletterService.newsletterUnsubscribe(params.email); } } diff --git a/src/categories/services/categories-accompagnement.service.spec.ts b/src/categories/services/categories-accompagnement.service.spec.ts index 2690850884d95e003009cd4395d8d7125889d03b..a889a46f2b6744ab3caed6d07a947a5c8f3399a2 100644 --- a/src/categories/services/categories-accompagnement.service.spec.ts +++ b/src/categories/services/categories-accompagnement.service.spec.ts @@ -1,11 +1,16 @@ import { getModelToken } from '@nestjs/mongoose'; import { Test, TestingModule } from '@nestjs/testing'; -import { CategoriesAccompagnement } from '../schemas/categoriesAccompagnement.schema'; +import { CreateCategoriesAccompagnement } from '../dto/create-categoriesAccompagnement.dto'; import { CategoriesAccompagnementService } from './categories-accompagnement.service'; describe('CategoriesAccompagnementService', () => { let service: CategoriesAccompagnementService; + const mockCategoriesAccompagnementModel = { + create: jest.fn(), + find: jest.fn(), + }; + beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ imports: [], @@ -13,7 +18,7 @@ describe('CategoriesAccompagnementService', () => { CategoriesAccompagnementService, { provide: getModelToken('CategoriesAccompagnement'), - useValue: CategoriesAccompagnement, + useValue: mockCategoriesAccompagnementModel, }, ], }).compile(); @@ -24,4 +29,20 @@ describe('CategoriesAccompagnementService', () => { it('should be defined', () => { expect(service).toBeDefined(); }); + + it('should create categorie accompagnement', async () => { + const data: CreateCategoriesAccompagnement = { name: 'test', modules: [{ id: 'Test', text: 'Text du test' }] }; + const _doc = { _id: '5fbb92ef80a5c257dc0161f2', ...data }; + mockCategoriesAccompagnementModel.create.mockResolvedValueOnce(_doc); + expect(await service.create(data)).toEqual(_doc); + }); + + it('should findAll structures', async () => { + const data: CreateCategoriesAccompagnement[] = [ + { name: 'test', modules: [{ id: 'Test', text: 'Text du test' }] }, + { name: 'test2', modules: [{ id: 'Test2', text: 'Text du test test 2' }] }, + ]; + mockCategoriesAccompagnementModel.find.mockResolvedValueOnce(data); + expect(await service.findAll()).toEqual(data); + }); }); diff --git a/src/categories/services/categories-accompagnement.service.ts b/src/categories/services/categories-accompagnement.service.ts index 830ad0c4552e0d68ccdccacda1f5bbbe0d63c3de..a8a41d17713196746f749b3e88c8dd9be0dbc328 100644 --- a/src/categories/services/categories-accompagnement.service.ts +++ b/src/categories/services/categories-accompagnement.service.ts @@ -11,11 +11,10 @@ export class CategoriesAccompagnementService { ) {} public async create(createDto: CreateCategoriesAccompagnement): Promise<CategoriesAccompagnement> { - const createdStructure = new this.structureModel(createDto); - return createdStructure.save(); + return this.structureModel.create(createDto); } public async findAll(): Promise<CategoriesAccompagnement[]> { - return this.structureModel.find().exec(); + return this.structureModel.find(); } } diff --git a/src/categories/services/categories-formations.service.spec.ts b/src/categories/services/categories-formations.service.spec.ts index 4f079fdd4611597a976174206557113ead3b3194..6f7a63bdf1bd3e9bd2639fe6475d87cacd5bdffc 100644 --- a/src/categories/services/categories-formations.service.spec.ts +++ b/src/categories/services/categories-formations.service.spec.ts @@ -1,11 +1,16 @@ import { getModelToken } from '@nestjs/mongoose'; import { Test, TestingModule } from '@nestjs/testing'; -import { CategoriesFormations } from '../schemas/categoriesFormations.schema'; +import { CreateCategoriesFormations } from '../dto/create-categoriesFormations.dto'; import { CategoriesFormationsService } from './categories-formations.service'; describe('CategoriesFormationsService', () => { let service: CategoriesFormationsService; + const mockCategoriesFormationModel = { + create: jest.fn(), + find: jest.fn(), + }; + beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ imports: [], @@ -13,7 +18,7 @@ describe('CategoriesFormationsService', () => { CategoriesFormationsService, { provide: getModelToken('CategoriesFormations'), - useValue: CategoriesFormations, + useValue: mockCategoriesFormationModel, }, ], }).compile(); @@ -24,4 +29,53 @@ describe('CategoriesFormationsService', () => { it('should be defined', () => { expect(service).toBeDefined(); }); + + it('should create categorie formation', async () => { + const data: CreateCategoriesFormations = { + name: 'test', + modules: [ + { + id: '2', + display_id: '2', + display_name: 'Modules APTIC - n°2', + text: 'Les conduites à risques et les bons usages du numérique', + digest: 'Les conduites à risques et les bons usages du numérique - 02', + }, + ], + }; + const _doc = { _id: '5fbb92ef80a5c257dc0161f2', ...data }; + mockCategoriesFormationModel.create.mockResolvedValueOnce(_doc); + expect(await service.create(data)).toEqual(_doc); + }); + + it('should findAll structures', async () => { + const data: CreateCategoriesFormations[] = [ + { + name: 'test', + modules: [ + { + id: '2', + display_id: '2', + display_name: 'Modules APTIC - n°2', + text: 'Les conduites à risques et les bons usages du numérique', + digest: 'Les conduites à risques et les bons usages du numérique - 02', + }, + ], + }, + { + name: 'test', + modules: [ + { + id: '1', + display_id: '1', + display_name: 'Modules APTIC - n°1', + text: 'Les conduites à risques et les bons usages', + digest: 'Les conduites à risques et les bons usages - 01', + }, + ], + }, + ]; + mockCategoriesFormationModel.find.mockResolvedValueOnce(data); + expect(await service.findAll()).toEqual(data); + }); }); diff --git a/src/categories/services/categories-formations.service.ts b/src/categories/services/categories-formations.service.ts index 71a6c1073ad5245541c651836b272195dd96397c..fab26f580d98135ce25f086a37136de2e2f9e095 100644 --- a/src/categories/services/categories-formations.service.ts +++ b/src/categories/services/categories-formations.service.ts @@ -9,15 +9,14 @@ export class CategoriesFormationsService { constructor(@InjectModel(CategoriesFormations.name) private structureModel: Model<CategoriesFormationsDocument>) {} public async create(createDto: CreateCategoriesFormations): Promise<CategoriesFormations> { - const createdStructure = new this.structureModel(createDto); - return createdStructure.save(); + return this.structureModel.create(createDto); } public async findAll(): Promise<CategoriesFormations[]> { - return this.structureModel.find().exec(); + return this.structureModel.find(); } - public findOne(categoryId: string): Promise<any> { + public findOne(categoryId: string): Promise<CategoriesFormations> { return this.structureModel.findOne({ id: categoryId }).select({ 'modules.id': 1 }).exec(); } } diff --git a/src/categories/services/categories-others.service.spec.ts b/src/categories/services/categories-others.service.spec.ts index 7c25eb3618f8bd75287bb29c7ebc52ba3256f33c..5de85c62c3669768f6064a8a2d54b2eb6a4a740e 100644 --- a/src/categories/services/categories-others.service.spec.ts +++ b/src/categories/services/categories-others.service.spec.ts @@ -1,11 +1,16 @@ import { getModelToken } from '@nestjs/mongoose'; import { Test, TestingModule } from '@nestjs/testing'; -import { CategoriesOthers } from '../schemas/categoriesOthers.schema'; +import { CreateCategoriesOthers } from '../dto/create-categoriesOthers.dto'; import { CategoriesOthersService } from './categories-others.service'; describe('CategoriesFormationsService', () => { let service: CategoriesOthersService; + const mockCategoriesOthersModel = { + create: jest.fn(), + find: jest.fn(), + }; + beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ imports: [], @@ -13,7 +18,7 @@ describe('CategoriesFormationsService', () => { CategoriesOthersService, { provide: getModelToken('CategoriesOthers'), - useValue: CategoriesOthers, + useValue: mockCategoriesOthersModel, }, ], }).compile(); @@ -24,4 +29,20 @@ describe('CategoriesFormationsService', () => { it('should be defined', () => { expect(service).toBeDefined(); }); + + it('should create categorie accompagnement', async () => { + const data: CreateCategoriesOthers = { name: 'test', modules: [{ id: 'Test', text: 'Text du test' }] }; + const _doc = { _id: '5fbb92ef80a5c257dc0161f2', ...data }; + mockCategoriesOthersModel.create.mockResolvedValueOnce(_doc); + expect(await service.create(data)).toEqual(_doc); + }); + + it('should findAll structures', async () => { + const data: CreateCategoriesOthers[] = [ + { name: 'test', modules: [{ id: 'Test', text: 'Text du test' }] }, + { name: 'test2', modules: [{ id: 'Test2', text: 'Text du test test 2' }] }, + ]; + mockCategoriesOthersModel.find.mockResolvedValueOnce(data); + expect(await service.findAll()).toEqual(data); + }); }); diff --git a/src/categories/services/categories-others.service.ts b/src/categories/services/categories-others.service.ts index d229648e6d1b6fd8c8d1ad1dd07d059048c9c1c3..fd8b1b68d36d720784c98be2211a2e94dc088fdf 100644 --- a/src/categories/services/categories-others.service.ts +++ b/src/categories/services/categories-others.service.ts @@ -9,11 +9,10 @@ export class CategoriesOthersService { constructor(@InjectModel(CategoriesOthers.name) private structureModel: Model<CategoriesOthersDocument>) {} public async create(createDto: CreateCategoriesOthers): Promise<CategoriesOthers> { - const createdStructure = new this.structureModel(createDto); - return createdStructure.save(); + return this.structureModel.create(createDto); } public async findAll(): Promise<CategoriesOthers[]> { - return this.structureModel.find().exec(); + return this.structureModel.find(); } } diff --git a/src/newsletter/newsletter.service.spec.ts b/src/newsletter/newsletter.service.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..15cfedd88581e257b359f3297886181982df19a3 --- /dev/null +++ b/src/newsletter/newsletter.service.spec.ts @@ -0,0 +1,137 @@ +import { HttpModule, HttpStatus } from '@nestjs/common'; +import { getModelToken } from '@nestjs/mongoose'; +import { Test, TestingModule } from '@nestjs/testing'; +import { INewsletterSubscription } from './interface/newsletter-subscription.interface'; +import { NewsletterSubscription } from './newsletter-subscription.schema'; +import { NewsletterService } from './newsletter.service'; +describe('NewsletterService', () => { + let service: NewsletterService; + + const mockNewsletterModel = { + create: jest.fn(), + deleteOne: jest.fn(), + countDocuments: jest.fn(), + findOne: jest.fn(), + exec: jest.fn(), + find: jest.fn(), + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [HttpModule], + providers: [ + NewsletterService, + { + provide: getModelToken(NewsletterSubscription.name), + useValue: mockNewsletterModel, + }, + ], + }).compile(); + + service = module.get<NewsletterService>(NewsletterService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + describe('newsletterSubscribe', () => { + it('it should not add subscription for email test2@test.com : already exist', async () => { + const result = { email: 'test2@test.com' } as INewsletterSubscription; + jest + .spyOn(service, 'findOne') + .mockImplementationOnce(async (): Promise<INewsletterSubscription | undefined> => result); + try { + await service.newsletterSubscribe('test2@test.com'); + // Fail test if above expression doesn't throw anything. + expect(true).toBe(false); + } catch (e) { + expect(e.message).toEqual('Email already exists'); + expect(e.status).toEqual(HttpStatus.BAD_REQUEST); + } + }); + it('it should add a subscription for email test2@test.com', async () => { + const result: INewsletterSubscription = { email: 'test2@test.com' } as INewsletterSubscription; + const _doc = { _id: 'a1aaaaa1a1', email: 'test2@test.com' }; + jest + .spyOn(service, 'findOne') + .mockImplementationOnce(async (): Promise<INewsletterSubscription | undefined> => undefined) + .mockImplementationOnce(async (): Promise<INewsletterSubscription | undefined> => result); + mockNewsletterModel.create.mockResolvedValueOnce(_doc); + + const subscription = await service.newsletterSubscribe('test2@test.com'); + expect(subscription).toEqual({ email: 'test2@test.com' }); + }); + }); + describe('newsletterUnsubscribe', () => { + it('it should not remove subscription for email test@test.com : does not exist', async () => { + const result: INewsletterSubscription = undefined; + jest + .spyOn(service, 'findOne') + .mockImplementationOnce(async (): Promise<INewsletterSubscription | undefined> => result); + try { + await service.newsletterUnsubscribe('test@test.com'); + // Fail test if above expression doesn't throw anything. + expect(true).toBe(false); + } catch (e) { + expect(e.message).toEqual('Invalid email'); + expect(e.status).toEqual(HttpStatus.BAD_REQUEST); + } + }); + it('it should remove a subscription for email test2@test.com', async () => { + const _doc = { _id: 'a1aaaaa1a1', email: 'test2@test.com' }; + const result = { + email: 'test2@test.com', + deleteOne: async () => _doc, + } as INewsletterSubscription; + jest + .spyOn(service, 'findOne') + .mockImplementationOnce(async (): Promise<INewsletterSubscription | undefined> => result); + + const subscription = await service.newsletterUnsubscribe('test2@test.com'); + expect(subscription).toEqual(_doc); + }); + }); + + describe('countNewsletterSubscriptions', () => { + it('it should count subscriptions', async () => { + mockNewsletterModel.countDocuments.mockResolvedValueOnce(69); + const count = await service.countNewsletterSubscriptions(); + + expect(count).toEqual(69); + }); + }); + + describe('findOne', () => { + it('it should not find a subscription with email test@test.com', async () => { + mockNewsletterModel.findOne.mockResolvedValueOnce(undefined); + const findOneEmail = await service.findOne('test@test.com'); + expect(findOneEmail).toEqual(undefined); + }); + it('it should find a subscription with email test2@test.com', async () => { + const _doc = { _id: 'a1aaaaa1a1', email: 'test2@test.com' } as INewsletterSubscription; + mockNewsletterModel.findOne.mockResolvedValueOnce(_doc); + const findOneEmail = await service.findOne('test2@test.com'); + expect(findOneEmail).toEqual(_doc); + }); + }); + describe('findAll', () => { + it('it should find all', async () => { + const _docs = [{ _id: 'a1aaaaa1a1', email: 'test2@test.com' } as INewsletterSubscription]; + mockNewsletterModel.find.mockResolvedValueOnce(_docs); + const findOneEmail = await service.findAll(); + expect(findOneEmail).toEqual(_docs); + }); + }); + describe('searchNewsletterSubscription', () => { + it('it should find 2 search result', async () => { + const _docs = [ + { _id: 'a1aaaaa1a1', email: 'test2@test.com' } as INewsletterSubscription, + { _id: 'bbbbb', email: 'test@test.com' } as INewsletterSubscription, + ]; + mockNewsletterModel.find.mockResolvedValueOnce(_docs); + const findOneEmail = await service.searchNewsletterSubscription('test'); + expect(findOneEmail.length).toBe(2); + }); + }); +}); diff --git a/src/newsletter/newsletter.service.ts b/src/newsletter/newsletter.service.ts index 89020eddbaa0808f25f0cedd9c30fba7bc29d1f0..c5f8363fbfc83d175b1fe5377e0de76386010e27 100644 --- a/src/newsletter/newsletter.service.ts +++ b/src/newsletter/newsletter.service.ts @@ -15,40 +15,31 @@ export class NewsletterService { if (existingEmail) { throw new HttpException('Email already exists', HttpStatus.BAD_REQUEST); } - const createSubscription = new this.newsletterSubscriptionModel({ email: email }); - createSubscription.save(); - return await this.findOne(email); + await this.newsletterSubscriptionModel.create({ email: email }); + return this.findOne(email); } public async newsletterUnsubscribe(email: string): Promise<NewsletterSubscription> { - const subscription = await this.newsletterSubscriptionModel.findOne({ email: email }).exec(); + const subscription = await this.findOne(email); if (!subscription) { throw new HttpException('Invalid email', HttpStatus.BAD_REQUEST); } return subscription.deleteOne(); } - public async findOne(mail: string): Promise<NewsletterSubscription | undefined> { - return this.newsletterSubscriptionModel.findOne({ email: mail }).exec(); + public async findOne(mail: string): Promise<INewsletterSubscription | undefined> { + return this.newsletterSubscriptionModel.findOne({ email: mail }); } public async searchNewsletterSubscription(searchString: string): Promise<NewsletterSubscriptionDocument[]> { - return this.newsletterSubscriptionModel.find({ email: new RegExp(searchString, 'i') }).exec(); + return this.newsletterSubscriptionModel.find({ email: new RegExp(searchString, 'i') }); } public async countNewsletterSubscriptions(): Promise<number> { - return this.newsletterSubscriptionModel.countDocuments({}).exec(); - } - - public async deleteOneEmail(mail: string): Promise<NewsletterSubscription | undefined> { - const subscription = await this.newsletterSubscriptionModel.findOne({ email: mail }).exec(); - if (!subscription) { - throw new HttpException('Invalid email', HttpStatus.BAD_REQUEST); - } - return subscription.deleteOne(); + return this.newsletterSubscriptionModel.countDocuments({}); } public async findAll(): Promise<NewsletterSubscription[]> { - return await this.newsletterSubscriptionModel.find().exec(); + return this.newsletterSubscriptionModel.find(); } } diff --git a/src/posts/posts.controller.spec.ts b/src/posts/posts.controller.spec.ts index 39b091029861448339fa4a8c755dd634664a6d77..56371cebb1628a0cb17190a7e65f98ea7e199903 100644 --- a/src/posts/posts.controller.spec.ts +++ b/src/posts/posts.controller.spec.ts @@ -1,17 +1,38 @@ -import { HttpModule } from '@nestjs/common'; +import { HttpModule, HttpService } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; +import { of } from 'rxjs'; import { ConfigurationModule } from '../configuration/configuration.module'; import { PostsController } from './posts.controller'; import { PostsService } from './posts.service'; -import { PostWithMeta } from './schemas/postWithMeta.schema'; +import { AxiosResponse } from 'axios'; describe('PostsController', () => { let controller: PostsController; + const httpServiceMock = { + get: jest.fn(), + }; + + const postServiceMock = { + getLocationTags: jest.fn(), + getPublicTags: jest.fn(), + getRegularTags: jest.fn(), + formatPosts: jest.fn(), + }; + beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ imports: [ConfigurationModule, HttpModule], - providers: [PostsService], + providers: [ + { + provide: PostsService, + useValue: postServiceMock, + }, + { + provide: HttpService, + useValue: httpServiceMock, + }, + ], controllers: [PostsController], }).compile(); @@ -22,23 +43,383 @@ describe('PostsController', () => { expect(controller).toBeDefined(); }); - it('should get pending attachments', async () => { - const result:PostWithMeta = {posts:[], meta:{pagination: null}}; - const query = ""; - jest.spyOn(controller, 'findAll').mockImplementation(async (): Promise<any> => result); - expect(await controller.findAll(query)).toBe(result); + describe('findAll', () => { + it('should get all posts', async () => { + const query = ''; + const result: AxiosResponse = { + data: { + posts: [ + { + id: '61c4847b0ff4550001505090', + uuid: 'f4ee5a37-a343-4cad-8a32-3f6cf87f9569', + title: 'Only feature image', + slug: 'only-feature-image', + html: '<p>Test</p>', + comment_id: '61c4847b0ff4550001505090', + feature_image: 'http://localhost:2368/content/images/2021/12/dacc-4.png', + featured: false, + visibility: 'public', + email_recipient_filter: 'none', + created_at: '2021-12-23T14:15:23.000+00:00', + updated_at: '2021-12-23T14:15:45.000+00:00', + published_at: '2021-12-23T14:15:45.000+00:00', + custom_excerpt: null, + codeinjection_head: null, + codeinjection_foot: null, + custom_template: null, + canonical_url: null, + tags: [Array], + authors: [Array], + primary_author: [Object], + primary_tag: [Object], + url: 'http://localhost:2368/only-feature-image/', + excerpt: + '« Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus.\n' + + 'Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed,\n' + + 'dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper\n' + + 'congue, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est\n' + + 'eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu\n' + + 'massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut\n' + + 'in risus volutpat libero pharetra tem', + reading_time: 2, + access: true, + send_email_when_published: false, + og_image: null, + og_title: null, + og_description: null, + twitter_image: null, + twitter_title: null, + twitter_description: null, + meta_title: null, + meta_description: null, + email_subject: null, + frontmatter: null, + }, + { + id: '61c4847b0ff4550001505090', + uuid: 'f4ee5a37-a343-4cad-8a32-3f6cf87f9569', + title: 'Only test image', + slug: 'only-test-image', + html: '<p>Test 2</p>', + comment_id: '61c4847b0ff4550001505090', + feature_image: 'http://localhost:2368/content/images/2021/12/test.png', + featured: false, + visibility: 'public', + email_recipient_filter: 'none', + created_at: '2021-12-23T14:15:23.000+00:00', + updated_at: '2021-12-23T14:15:45.000+00:00', + published_at: '2021-12-23T14:15:45.000+00:00', + custom_excerpt: null, + codeinjection_head: null, + codeinjection_foot: null, + custom_template: null, + canonical_url: null, + tags: [Array], + authors: [Array], + primary_author: [Object], + primary_tag: [Object], + url: 'http://localhost:2368/only-feature-image/', + excerpt: + '« Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus.\n' + + 'Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed,\n' + + 'dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper\n' + + 'congue, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est\n' + + 'eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu\n' + + 'massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut\n' + + 'in risus volutpat libero pharetra tem', + reading_time: 2, + access: true, + send_email_when_published: false, + og_image: null, + og_title: null, + og_description: null, + twitter_image: null, + twitter_title: null, + twitter_description: null, + meta_title: null, + meta_description: null, + email_subject: null, + frontmatter: null, + }, + { + id: '61c4847b0ff4550001505090', + uuid: 'f4ee5a37-a343-4cad-8a32-3f6cf87f9569', + title: 'Only toto image', + slug: 'only-toto-image', + html: '<p>Test 3</p>', + comment_id: '61c4847b0ff4550001505090', + feature_image: 'http://localhost:2368/content/images/2021/12/dacc-4.png', + featured: false, + visibility: 'public', + email_recipient_filter: 'none', + created_at: '2021-12-23T14:15:23.000+00:00', + updated_at: '2021-12-23T14:15:45.000+00:00', + published_at: '2021-12-23T14:15:45.000+00:00', + custom_excerpt: null, + codeinjection_head: null, + codeinjection_foot: null, + custom_template: null, + canonical_url: null, + tags: [Array], + authors: [Array], + primary_author: [Object], + primary_tag: [Object], + url: 'http://localhost:2368/only-feature-image/', + excerpt: + '« Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus.\n' + + 'Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed,\n' + + 'dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper\n' + + 'congue, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est\n' + + 'eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu\n' + + 'massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut\n' + + 'in risus volutpat libero pharetra tem', + reading_time: 2, + access: true, + send_email_when_published: false, + og_image: null, + og_title: null, + og_description: null, + twitter_image: null, + twitter_title: null, + twitter_description: null, + meta_title: null, + meta_description: null, + email_subject: null, + frontmatter: null, + }, + ], + meta: { pagination: { page: 1, limit: 15, pages: 1, total: 2, next: null, prev: null } }, + }, + status: 200, + statusText: 'OK', + headers: {}, + config: {}, + }; + jest.spyOn(httpServiceMock, 'get').mockImplementationOnce(() => of(result)); + const response = await controller.findAll(query); + expect(response.posts.length).toEqual(3); + expect(response.meta.pagination.limit).toEqual(15); + expect(response.meta.pagination.page).toEqual(1); + expect(response.meta.pagination.total).toEqual(2); + }); }); - it('should get pending attachments', async () => { - const result = { posts:[] }; - jest.spyOn(controller, 'findAllTags').mockImplementation(async (): Promise<any> => result); - expect(await controller.findAllTags()).toBe(result); + describe('findAllTags', () => { + it('should get all tags', async () => { + postServiceMock.getLocationTags.mockImplementationOnce(() => [ + { + id: '61b74b2c0d3b9800018ca3df', + name: 'oullins', + slug: 'oullins', + description: 'commune', + feature_image: null, + visibility: 'public', + og_image: null, + og_title: null, + og_description: null, + twitter_image: null, + twitter_title: null, + twitter_description: null, + meta_title: null, + meta_description: null, + codeinjection_head: null, + codeinjection_foot: null, + canonical_url: null, + accent_color: null, + created_at: '2021-12-13T13:31:24.000Z', + updated_at: '2021-12-13T13:31:25.000Z', + url: 'http://localhost:2368/tag/oullins/', + }, + { + id: '61b74b2c0d3b9800018ca3df', + name: 'oullins', + slug: 'oullins', + description: 'commune', + feature_image: null, + visibility: 'public', + og_image: null, + og_title: null, + og_description: null, + twitter_image: null, + twitter_title: null, + twitter_description: null, + meta_title: null, + meta_description: null, + codeinjection_head: null, + codeinjection_foot: null, + canonical_url: null, + accent_color: null, + created_at: '2021-12-13T13:31:24.000Z', + updated_at: '2021-12-13T13:31:25.000Z', + url: 'http://localhost:2368/tag/oullins/', + }, + { + id: '61b74b2c0d3b9800018ca3e0', + name: 'Bron', + slug: 'bron', + description: 'commune', + feature_image: null, + visibility: 'public', + og_image: null, + og_title: null, + og_description: null, + twitter_image: null, + twitter_title: null, + twitter_description: null, + meta_title: null, + meta_description: null, + codeinjection_head: null, + codeinjection_foot: null, + canonical_url: null, + accent_color: null, + created_at: '2021-12-13T13:31:24.000Z', + updated_at: '2021-12-13T13:31:24.000Z', + url: 'http://localhost:2368/404/', + }, + ]); + postServiceMock.getPublicTags.mockImplementationOnce(() => [ + { + id: '61b74b2d0d3b9800018ca3f6', + name: 'Séniors (+ de 65ans)', + slug: 'seniors-de-65ans', + description: 'public', + feature_image: null, + visibility: 'public', + og_image: null, + og_title: null, + og_description: null, + twitter_image: null, + twitter_title: null, + twitter_description: null, + meta_title: null, + meta_description: null, + codeinjection_head: null, + codeinjection_foot: null, + canonical_url: null, + accent_color: null, + created_at: '2021-12-13T13:31:25.000Z', + updated_at: '2021-12-13T13:31:25.000Z', + url: 'http://localhost:2368/404/', + }, + { + id: '61b74b2d0d3b9800018ca3f7', + name: 'Allophones', + slug: 'allophones', + description: 'public', + feature_image: null, + visibility: 'public', + og_image: null, + og_title: null, + og_description: null, + twitter_image: null, + twitter_title: null, + twitter_description: null, + meta_title: null, + meta_description: null, + codeinjection_head: null, + codeinjection_foot: null, + canonical_url: null, + accent_color: null, + created_at: '2021-12-13T13:31:25.000Z', + updated_at: '2021-12-13T13:31:25.000Z', + url: 'http://localhost:2368/404/', + }, + ]); + postServiceMock.getRegularTags.mockImplementationOnce(() => [ + { + id: '61b74b2d0d3b9800018ca3fa', + name: 'Études', + slug: 'etudes', + description: null, + feature_image: null, + visibility: 'public', + og_image: null, + og_title: null, + og_description: null, + twitter_image: null, + twitter_title: null, + twitter_description: null, + meta_title: null, + meta_description: null, + codeinjection_head: null, + codeinjection_foot: null, + canonical_url: null, + accent_color: null, + created_at: '2021-12-13T13:31:25.000Z', + updated_at: '2021-12-13T13:31:25.000Z', + url: 'http://localhost:2368/tag/etudes/', + }, + ]); + const result = await controller.findAllTags(); + expect(result.commune.length).toBe(3); + expect(result.others.length).toBe(1); + expect(result.public.length).toBe(2); + }); }); - it('should get pending attachments', async () => { - const result = { public:[], comune:[], others:[] }; - const id = "78945945" - jest.spyOn(controller, 'getPostbyId').mockImplementation(async (): Promise<any> => result); - expect(await controller.getPostbyId(id)).toBe(result); + describe('getPostbyId', () => { + it('should get post Hello by id 61c4847b0ff4550001505090', async () => { + const data = [ + { + id: '61c4847b0ff4550001505090', + uuid: 'f4ee5a37-a343-4cad-8a32-3f6cf87f9569', + title: 'Hello', + slug: 'hello', + html: '<p>Test</p>', + comment_id: '61c4847b0ff4550001505090', + feature_image: 'http://localhost:2368/content/images/2021/12/dacc-4.png', + featured: false, + visibility: 'public', + email_recipient_filter: 'none', + created_at: '2021-12-23T14:15:23.000+00:00', + updated_at: '2021-12-23T14:15:45.000+00:00', + published_at: '2021-12-23T14:15:45.000+00:00', + custom_excerpt: null, + codeinjection_head: null, + codeinjection_foot: null, + custom_template: null, + canonical_url: null, + tags: [Array], + authors: [Array], + primary_author: [Object], + primary_tag: [Object], + url: 'http://localhost:2368/hello/', + excerpt: + '« Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus.\n' + + 'Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed,\n' + + 'dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper\n' + + 'congue, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est\n' + + 'eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu\n' + + 'massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut\n' + + 'in risus volutpat libero pharetra tem', + reading_time: 2, + access: true, + send_email_when_published: false, + og_image: null, + og_title: null, + og_description: null, + twitter_image: null, + twitter_title: null, + twitter_description: null, + meta_title: null, + meta_description: null, + email_subject: null, + frontmatter: null, + }, + ]; + const axiosResult: AxiosResponse = { + data: { + posts: data, + }, + status: 200, + statusText: 'OK', + headers: {}, + config: {}, + }; + httpServiceMock.get.mockImplementationOnce(() => of(axiosResult)); + postServiceMock.formatPosts.mockImplementationOnce(() => data); + const result = await (await controller.getPostbyId('61c4847b0ff4550001505090')).toPromise(); + expect(result).toStrictEqual({ posts: [data] }); + }); }); }); diff --git a/src/posts/posts.controller.ts b/src/posts/posts.controller.ts index d947390c55e7a81214f5228c8d558d23dab0b2a8..0652fc5ae92355154d2778545387241f17ac6a53 100644 --- a/src/posts/posts.controller.ts +++ b/src/posts/posts.controller.ts @@ -9,6 +9,7 @@ import { PostWithMeta } from './schemas/postWithMeta.schema'; @Controller('posts') export class PostsController { + private readonly logger = new Logger(PostsController.name); constructor(private readonly httpService: HttpService, private readonly postsService: PostsService) {} @Get() @@ -36,7 +37,7 @@ export class PostsController { .pipe( map((response) => response.data), catchError((err) => { - Logger.error(err); + this.logger.error(err); throw new HttpException('Invalid ghost configuration', HttpStatus.BAD_REQUEST); }) ); diff --git a/src/posts/posts.service.spec.ts b/src/posts/posts.service.spec.ts index 5b01e555650ff0b0f58f1a33e8f3f352846d4fbe..90d17e16278895ed92a474666fda924c18c171d9 100644 --- a/src/posts/posts.service.spec.ts +++ b/src/posts/posts.service.spec.ts @@ -1,10 +1,210 @@ import { HttpModule } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { ConfigurationModule } from '../configuration/configuration.module'; +import { TagEnum } from './enums/tag.enum'; import { PostsService } from './posts.service'; +import { Tag } from './schemas/tag.schema'; +import * as _ from 'lodash'; +import { Post } from './schemas/post.schema'; describe('PostsService', () => { let service: PostsService; + // let configService: ConfigurationServiceMock; + + const locationtags = [ + { + id: '61b74b2c0b9800018ca3df', + name: 'oullins', + slug: 'oullins', + description: 'commune', + feature_image: null, + visibility: 'public', + og_image: null, + og_title: null, + og_description: null, + twitter_image: null, + twitter_title: null, + twitter_description: null, + meta_title: null, + meta_description: null, + codeinjection_head: null, + codeinjection_foot: null, + canonical_url: null, + accent_color: null, + created_at: '2021-12-13T13:31:24.000Z', + updated_at: '2021-12-13T13:31:25.000Z', + url: 'http://localhost:2368/tag/oullins/', + }, + { + id: '61b74b2c0d9800018ca3df', + name: 'oullins', + slug: 'oullins', + description: 'commune', + feature_image: null, + visibility: 'public', + og_image: null, + og_title: null, + og_description: null, + twitter_image: null, + twitter_title: null, + twitter_description: null, + meta_title: null, + meta_description: null, + codeinjection_head: null, + codeinjection_foot: null, + canonical_url: null, + accent_color: null, + created_at: '2021-12-13T13:31:24.000Z', + updated_at: '2021-12-13T13:31:25.000Z', + url: 'http://localhost:2368/tag/oullins/', + }, + { + id: '61b74b2d3b9800018ca3e0', + name: 'Bron', + slug: 'bron', + description: 'commune', + feature_image: null, + visibility: 'public', + og_image: null, + og_title: null, + og_description: null, + twitter_image: null, + twitter_title: null, + twitter_description: null, + meta_title: null, + meta_description: null, + codeinjection_head: null, + codeinjection_foot: null, + canonical_url: null, + accent_color: null, + created_at: '2021-12-13T13:31:24.000Z', + updated_at: '2021-12-13T13:31:24.000Z', + url: 'http://localhost:2368/404/', + }, + ]; + + const publictags = [ + { + id: '61b74b2d0d3b9800018ca3f6', + name: 'Séniors (+ de 65ans)', + slug: 'seniors-de-65ans', + description: 'public', + feature_image: null, + visibility: 'public', + og_image: null, + og_title: null, + og_description: null, + twitter_image: null, + twitter_title: null, + twitter_description: null, + meta_title: null, + meta_description: null, + codeinjection_head: null, + codeinjection_foot: null, + canonical_url: null, + accent_color: null, + created_at: '2021-12-13T13:31:25.000Z', + updated_at: '2021-12-13T13:31:25.000Z', + url: 'http://localhost:2368/404/', + }, + { + id: '61b74b2d0d3b98000ca3f7', + name: 'Allophones', + slug: 'allophones', + description: 'public', + feature_image: null, + visibility: 'public', + og_image: null, + og_title: null, + og_description: null, + twitter_image: null, + twitter_title: null, + twitter_description: null, + meta_title: null, + meta_description: null, + codeinjection_head: null, + codeinjection_foot: null, + canonical_url: null, + accent_color: null, + created_at: '2021-12-13T13:31:25.000Z', + updated_at: '2021-12-13T13:31:25.000Z', + url: 'http://localhost:2368/404/', + }, + ]; + + const otherTags = [ + { + id: '61b74b2d0d3b980001a3fa', + name: 'Études', + slug: 'etudes', + description: null, + feature_image: null, + visibility: 'public', + og_image: null, + og_title: null, + og_description: null, + twitter_image: null, + twitter_title: null, + twitter_description: null, + meta_title: null, + meta_description: null, + codeinjection_head: null, + codeinjection_foot: null, + canonical_url: null, + accent_color: null, + created_at: '2021-12-13T13:31:25.000Z', + updated_at: '2021-12-13T13:31:25.000Z', + url: 'http://localhost:2368/tag/etudes/', + }, + { + id: '61b74b2d0d3b980001a3fa', + name: 'A la une', + slug: 'a-la-une', + description: null, + feature_image: null, + visibility: 'public', + og_image: null, + og_title: null, + og_description: null, + twitter_image: null, + twitter_title: null, + twitter_description: null, + meta_title: null, + meta_description: null, + codeinjection_head: null, + codeinjection_foot: null, + canonical_url: null, + accent_color: null, + created_at: '2021-12-13T13:31:25.000Z', + updated_at: '2021-12-13T13:31:25.000Z', + url: 'http://localhost:2368/tag/a-la-une/', + }, + { + id: '61b74b2d0d3b980001a3fa', + name: 'Infos', + slug: 'infos', + description: null, + feature_image: null, + visibility: 'public', + og_image: null, + og_title: null, + og_description: null, + twitter_image: null, + twitter_title: null, + twitter_description: null, + meta_title: null, + meta_description: null, + codeinjection_head: null, + codeinjection_foot: null, + canonical_url: null, + accent_color: null, + created_at: '2021-12-13T13:31:25.000Z', + updated_at: '2021-12-13T13:31:25.000Z', + url: 'http://localhost:2368/tag/infos/', + }, + ]; + + const tagsData = [...locationtags, ...publictags, ...otherTags]; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -13,9 +213,114 @@ describe('PostsService', () => { }).compile(); service = module.get<PostsService>(PostsService); + jest.spyOn(service, 'getTags').mockImplementationOnce(async (): Promise<Tag[]> => tagsData); }); it('should be defined', () => { expect(service).toBeDefined(); }); + + it('should get tags', async () => { + expect(await service.getTags()).toEqual(tagsData); + }); + + it('should get location tags', async () => { + expect(await service.getLocationTags()).toEqual(locationtags); + }); + + it('should get public tags', async () => { + expect(await service.getPublicTags()).toEqual(publictags); + }); + + it('should get regular tags', async () => { + expect(await service.getRegularTags()).toEqual([otherTags[1], otherTags[2], otherTags[0]]); + }); + describe('arraymove', () => { + it('should order tags for display: A La Une tag should be set to first index', () => { + const dataCopy = [...otherTags]; + service.arraymove(dataCopy, _.findIndex(dataCopy, { slug: TagEnum.aLaUne }), 0); + expect(dataCopy).toEqual([otherTags[1], otherTags[0], otherTags[2]]); + }); + it('should order tags for display: Info tag should be set to second index', () => { + const dataCopy = [...otherTags]; + expect(service.arraymove(dataCopy, _.findIndex(dataCopy, { slug: TagEnum.infos }), 1)).toEqual([ + otherTags[0], + otherTags[2], + otherTags[1], + ]); + }); + it('should order tags for display: A La Une tag should be set to first index and Info to second', () => { + const dataCopy = [...otherTags]; + service.arraymove(dataCopy, _.findIndex(dataCopy, { slug: TagEnum.aLaUne }), 0); + expect(service.arraymove(dataCopy, _.findIndex(dataCopy, { slug: TagEnum.infos }), 1)).toEqual([ + otherTags[1], + otherTags[2], + otherTags[0], + ]); + }); + }); + + describe('formatPosts', () => { + const postToFormat: Post = { + id: '61c4847b0ff4550001505090', + uuid: 'f4ee5a37-a343-4cad-8a32-3f6cf87f9569', + title: 'Only feature image', + slug: 'only-feature-image', + html: '<p>Test</p>', + comment_id: '61c4847b0ff4550001505090', + feature_image: 'http://localhost:2368/content/images/2021/12/dacc-4.png', + featured: false, + visibility: 'public', + email_recipient_filter: 'none', + created_at: '2021-12-23T14:15:23.000+00:00', + updated_at: '2021-12-23T14:15:45.000+00:00', + published_at: '2021-12-23T14:15:45.000+00:00', + custom_excerpt: null, + codeinjection_head: null, + codeinjection_foot: null, + custom_template: null, + canonical_url: null, + tags: [], + authors: [], + primary_author: [], + primary_tag: [], + url: 'http://localhost:2368/only-feature-image/', + excerpt: + '« Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus.\n' + + 'Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed,\n' + + 'dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper\n' + + 'congue, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est\n' + + 'eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu\n' + + 'massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut\n' + + 'in risus volutpat libero pharetra tem', + reading_time: '2', + access: true, + send_email_when_published: false, + og_image: null, + og_title: null, + og_description: null, + twitter_image: null, + twitter_title: null, + twitter_description: null, + meta_title: null, + meta_description: null, + email_subject: null, + frontmatter: null, + }; + it('should format post with no custom expert', () => { + const objCp = { ...postToFormat }; + let result = { ...postToFormat }; + result.excerpt = 'Inconnu'; + result.feature_image = 'https://localhost/blog/content/images/2021/12/dacc-4.png'; + expect(service.formatPosts(objCp)).toEqual(result); + }); + it('should format post with custom expert', () => { + let objCp = { ...postToFormat }; + objCp.custom_excerpt = 'Toto'; + let result = { ...postToFormat }; + result.custom_excerpt = 'Toto'; + result.feature_image = 'https://localhost/blog/content/images/2021/12/dacc-4.png'; + expect(service.formatPosts(objCp)).toEqual(result); + }); + }); }); diff --git a/src/posts/schemas/post.schema.ts b/src/posts/schemas/post.schema.ts index 3e2ac2ca992fc0be11e97e73b2e13f6730a6aa75..6e2e8cc22f9bd07f2d0c75f0c3327d7e2a91b10a 100644 --- a/src/posts/schemas/post.schema.ts +++ b/src/posts/schemas/post.schema.ts @@ -27,4 +27,13 @@ export class Post { meta_title: string; meta_description: string; email_subject: string; + codeinjection_head: string; + codeinjection_foot: string; + custom_template: string; + canonical_url: string; + tags: []; + authors: []; + primary_author: []; + primary_tag: []; + frontmatter: []; } diff --git a/src/structures/services/aptic-structures.service.ts b/src/structures/services/aptic-structures.service.ts index 46098d0e83ec09f6217ff562f3318f7c77b1c89f..978a2ecf9d3093ec35e75498b9c863db7cfd6113 100644 --- a/src/structures/services/aptic-structures.service.ts +++ b/src/structures/services/aptic-structures.service.ts @@ -17,6 +17,7 @@ import { StructuresSearchService } from './structures-search.service'; @Injectable() export class ApticStructuresService { + private readonly logger = new Logger(ApticStructuresService.name); constructor( private readonly httpService: HttpService, private readonly userService: UsersService, @@ -46,13 +47,15 @@ export class ApticStructuresService { this.createApticStructures(structure); }, (err) => { - Logger.log(err); + this.logger.log(err); } ); }); }, (err) => { - Logger.log(`getApticStructures error on postal code: ${postalCode}. Code: ${err}`); + this.logger.log( + `formatApticStructures | getApticStructures error on postal code: ${postalCode}. Code: ${err}` + ); } ); }); @@ -65,7 +68,7 @@ export class ApticStructuresService { private async createApticStructures(structure: ApticStructure): Promise<any> { this.structureAlreadyExist(structure).then(async (exist) => { if (!exist) { - Logger.log(`Create structure : ${structure.name}`, 'ApticStructuresService - createApticStructures'); + this.logger.log(`createApticStructures | Create structure : ${structure.name}`); const createdStructure = new this.structureModel(); // Known fields createdStructure.structureName = structure.name; @@ -183,7 +186,7 @@ export class ApticStructuresService { public getMetopoleMunicipality(): void { const req = 'https://download.data.grandlyon.com/ws/grandlyon/adr_voie_lieu.adrcomgl/all.json?maxfeatures=-1&start=1'; - Logger.log(`Request : ${req}`, 'ApticStructuresService - getMetopoleMunicipality'); + this.logger.log(`getMetopoleMunicipality | Request : ${req}`, ''); this.httpService.get(encodeURI(req)).subscribe( (data) => { const inseeArray = data.data.values.map((municipality) => { @@ -196,19 +199,19 @@ export class ApticStructuresService { this.formatApticStructures(postalCodeArray); }); }, - (err) => Logger.error(err) + (err) => this.logger.error(err) ); } public getPostalCodeWithINSEE(inseeCode: string): Observable<AxiosResponse<any>> { const req = `https://geo.api.gouv.fr/communes/${inseeCode}?fields=codesPostaux&format=json`; - Logger.log(`Request : ${req}`, 'ApticStructuresService - getMetopoleMunicipality'); + this.logger.debug(`getMetopoleMunicipality | Request : ${req}`); return this.httpService.get(encodeURI(req)); } public getApticStructures(postalCodeData: string): Observable<AxiosResponse<{ presencePoints: ApticStructure[] }>> { const req = `https://aptisearch-api.aptic.fr/v1/postal-code/${postalCodeData}`; - Logger.log(`Request : ${req}`, 'ApticStructuresService'); + this.logger.debug(`getApticStructures | Request : ${req}`); return this.httpService.get(req, { headers: { api_key: process.env.APTIC_TOKEN, @@ -221,7 +224,7 @@ export class ApticStructuresService { rejectUnauthorized: false, }); const req = `https://aptisearch-api.aptic.fr/v1/catalog/${catalogId}/services`; - Logger.log(`Request : ${req}`, 'ApticStructuresService'); + this.logger.log(`getApticStructureOffer | Request : ${req}`); return this.httpService.get(req, { httpsAgent: agent, headers: { diff --git a/src/structures/services/structure.service.spec.ts b/src/structures/services/structure.service.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..82128e4105f934f8062832a0b90b5cc21b25ab4e --- /dev/null +++ b/src/structures/services/structure.service.spec.ts @@ -0,0 +1,109 @@ +import { HttpModule, HttpStatus } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { ElasticsearchService } from '@nestjs/elasticsearch'; +import { getModelToken } from '@nestjs/mongoose'; +import { Test, TestingModule } from '@nestjs/testing'; +import { UsersServiceMock } from '../../../test/mock/services/user.mock.service'; +import { ConfigurationService } from '../../configuration/configuration.service'; +import { MailerModule } from '../../mailer/mailer.module'; +import { MailerService } from '../../mailer/mailer.service'; +import { SearchModule } from '../../search/search.module'; +import { UsersService } from '../../users/users.service'; +import { structureDto } from '../dto/structure.dto'; +import { Structure } from '../schemas/structure.schema'; +import { StructuresSearchService } from './structures-search.service'; +import { StructuresService } from './structures.service'; +describe('StructuresService', () => { + let service: StructuresService; + + const mockStructureModel = { + create: jest.fn(), + deleteOne: jest.fn(), + countDocuments: jest.fn(), + findOne: jest.fn(), + exec: jest.fn(), + find: jest.fn(), + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [HttpModule, MailerModule, SearchModule, ConfigModule], + providers: [ + StructuresService, + ConfigurationService, + StructuresSearchService, + { + provide: getModelToken(Structure.name), + useValue: mockStructureModel, + }, + { + provide: UsersService, + useClass: UsersServiceMock, + }, + ], + }).compile(); + + service = module.get<StructuresService>(StructuresService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + + it('should Initiate structure', () => { + const res = service.initiateStructureIndex(); + expect(res).toBeTruthy(); + }); + + it('should searchForStructures', () => { + let res = service.searchForStructures('a', [{ nbPrinters: '1' }]); + expect(res).toBeTruthy(); + res = service.searchForStructures('a'); + expect(res).toBeTruthy(); + }); + + it('should create structure', () => { + const structure = new structureDto(); + let res = service.create(null, structure); + expect(res).toBeTruthy(); + res = service.create('tsfsf6296', structure); + expect(res).toBeTruthy(); + }); + + it('should search structure', () => { + const filters = [{ nbPrinters: '1' }]; + let res = service.search('', filters); + expect(res).toBeTruthy(); + res = service.search(null, filters); + expect(res).toBeTruthy(); + res = service.search(null); + expect(res).toBeTruthy(); + }); + + it('should find all structures', () => { + const res = service.findAll(); + expect(res).toBeTruthy(); + }); + + it('should find all unclaimed structures', () => { + const res = service.findAllUnclaimed(); + expect(res).toBeTruthy(); + }); + + it('should find all formated structures', () => { + const res = service.findAllFormated(null, null, null); + expect(res).toBeTruthy(); + }); + + it('should populate ES', () => { + const res = service.populateES(); + expect(res).toBeTruthy(); + }); + + it('should report structure Error', () => { + let res = service.reportStructureError('6093ba0e2ab5775cfc01ed3e', ''); + expect(res).toBeTruthy(); + res = service.reportStructureError(null, ''); + expect(res).toBeTruthy(); + }); +}); diff --git a/src/tcl/tclStopPoint.service.spec.ts b/src/tcl/tclStopPoint.service.spec.ts deleted file mode 100644 index 2c6d4220f3f2a135aabaa0b0ec423dab6713a53b..0000000000000000000000000000000000000000 --- a/src/tcl/tclStopPoint.service.spec.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { HttpModule } from '@nestjs/common'; -import { getModelToken } from '@nestjs/mongoose'; -import { Test, TestingModule } from '@nestjs/testing'; -import { TclStopPoint } from './tclStopPoint.schema'; -import { TclStopPointService } from './tclStopPoint.service'; - -describe('TclService', () => { - let service: TclStopPointService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - imports: [HttpModule], - providers: [ - TclStopPointService, - { - provide: getModelToken('TclStopPoint'), - useValue: TclStopPoint, - }, - ], - }).compile(); - - service = module.get<TclStopPointService>(TclStopPointService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); diff --git a/src/temp-user/temp-user.controller.spec.ts b/src/temp-user/temp-user.controller.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..9005d4c87a29559f813af9a1e7c0752fc85c1e6f --- /dev/null +++ b/src/temp-user/temp-user.controller.spec.ts @@ -0,0 +1,47 @@ +import { HttpModule, HttpStatus } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { MailerModule } from '../mailer/mailer.module'; +import { TempUserController } from './temp-user.controller'; +import { TempUserService } from './temp-user.service'; + +describe('TempUserService', () => { + let controller: TempUserController; + + const mockTempUserService = { + findById: jest.fn(), + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [HttpModule, MailerModule], + providers: [{ provide: TempUserService, useValue: mockTempUserService }], + controllers: [TempUserController], + }).compile(); + + controller = module.get<TempUserController>(TempUserController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); + + describe('getTempUser', () => { + it('should get temporary users', async () => { + const tmpUser = { email: 'test@test.com', pendingStructuresLink: [] }; + mockTempUserService.findById.mockReturnValueOnce(tmpUser); + expect(await controller.getTempUser('addq651')).toEqual(tmpUser); + }); + it('should throw error in cas of no users', async () => { + const tmpUser = null; + mockTempUserService.findById.mockReturnValueOnce(tmpUser); + try { + await controller.getTempUser('addq651'); + // Fail test if above expression doesn't throw anything. + expect(true).toBe(false); + } catch (e) { + expect(e.message).toEqual('User does not exists'); + expect(e.status).toEqual(HttpStatus.BAD_REQUEST); + } + }); + }); +}); diff --git a/src/temp-user/temp-user.interface.ts b/src/temp-user/temp-user.interface.ts index 1e0a132a1bb71e3fdd88ddc3bd5262e82e0c085e..2a4e74ff88ff5ec98d24b32178e84ac01bcb494b 100644 --- a/src/temp-user/temp-user.interface.ts +++ b/src/temp-user/temp-user.interface.ts @@ -1,7 +1,6 @@ import { Document, Types } from 'mongoose'; export interface ITempUser extends Document { - readonly _id: string; email: string; - pendingStructuresLink: Types.ObjectId[]; + pendingStructuresLink?: Types.ObjectId[]; } diff --git a/src/temp-user/temp-user.schema.ts b/src/temp-user/temp-user.schema.ts index dc70ce2ac22516550162e44b51d4d2523164aa82..3112b3112f6217e5f5ab30242257aece39726a51 100644 --- a/src/temp-user/temp-user.schema.ts +++ b/src/temp-user/temp-user.schema.ts @@ -9,7 +9,7 @@ export class TempUser { email: string; @Prop({ default: null }) - pendingStructuresLink: Types.ObjectId[]; + pendingStructuresLink?: Types.ObjectId[]; } export const TempUserSchema = SchemaFactory.createForClass(TempUser); diff --git a/src/temp-user/temp-user.service.spec.ts b/src/temp-user/temp-user.service.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..14c2995c97c9c3497760956e0968aa0cc7497da8 --- /dev/null +++ b/src/temp-user/temp-user.service.spec.ts @@ -0,0 +1,107 @@ +import { HttpModule, HttpStatus } from '@nestjs/common'; +import { getModelToken } from '@nestjs/mongoose'; +import { Test, TestingModule } from '@nestjs/testing'; +import { MailerModule } from '../mailer/mailer.module'; +import { TempUserService } from './temp-user.service'; + +describe('TempUserService', () => { + let service: TempUserService; + + const tempUserModelMock = { + create: jest.fn(), + findOne: jest.fn(), + findById: jest.fn(), + deleteOne: jest.fn(), + find: jest.fn(), + exec: jest.fn(), + updateOne: jest.fn(), + }; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [HttpModule, MailerModule], + providers: [ + TempUserService, + { + provide: getModelToken('TempUser'), + useValue: tempUserModelMock, + }, + ], + }).compile(); + + service = module.get<TempUserService>(TempUserService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); + describe('create', () => { + const tmpUser = { email: 'test@test.com', pendingStructuresLink: [] }; + it('should not create temporary user: already exist', async () => { + tempUserModelMock.findOne.mockResolvedValueOnce(tmpUser); + try { + await service.create(tmpUser, 'PIMMS Framboise'); + expect(true).toBe(false); + } catch (e) { + expect(e.message).toEqual('User already exists'); + expect(e.status).toEqual(HttpStatus.BAD_REQUEST); + } + }); + it('should create temporary user', async () => { + tempUserModelMock.findOne.mockResolvedValueOnce(null).mockResolvedValueOnce(tmpUser); + tempUserModelMock.create.mockResolvedValueOnce(tmpUser); + expect(await service.create(tmpUser, 'PIMMS Framboise')).toEqual(tmpUser); + }); + }); + it('should find one', async () => { + const tmpUser = { email: 'test2@test.com', pendingStructuresLink: [] }; + tempUserModelMock.findOne.mockResolvedValueOnce(tmpUser); + expect(await service.findOne('test2@test.com')).toEqual(tmpUser); + }); + + it('should find one by id', async () => { + const tmpUser = { email: 'test2@test.com', pendingStructuresLink: [] }; + tempUserModelMock.findById.mockResolvedValueOnce(tmpUser); + expect(await service.findById('5fbb92e480a5c257dc0161f0')).toEqual(tmpUser); + }); + + describe('delete', () => { + it('should delete a temp user', async () => { + const tmpUser = { email: 'test2@test.com', pendingStructuresLink: [] }; + tempUserModelMock.findOne.mockResolvedValueOnce(tmpUser); + tempUserModelMock.deleteOne.mockImplementationOnce(() => {}); + expect(await service.delete('toto@test.com')).toEqual(tmpUser); + }); + it('should return an error : user does not exist', async () => { + tempUserModelMock.findOne.mockResolvedValueOnce(null); + try { + await service.delete('toto@test.com'); + expect(true).toBe(false); + } catch (e) { + expect(e.message).toEqual('User does not exists'); + expect(e.status).toEqual(HttpStatus.BAD_REQUEST); + } + }); + }); + + describe('updateStructureLinked', () => { + it('should update structure linked', async () => { + const tmpUser = { email: 'test2@test.com', pendingStructuresLink: [] }; + tempUserModelMock.find.mockReturnThis(); + tempUserModelMock.exec.mockResolvedValueOnce([]).mockResolvedValueOnce(tmpUser); + tempUserModelMock.updateOne.mockReturnThis(); + expect(await service.updateStructureLinked(tmpUser)).toEqual(tmpUser); + }); + it('should not update structure linked: User already linked', async () => { + const tmpUser = { email: 'test2@test.com', pendingStructuresLink: [] }; + tempUserModelMock.find.mockReturnThis(); + tempUserModelMock.exec.mockResolvedValueOnce([tmpUser]); + try { + await service.updateStructureLinked(tmpUser); + } catch (e) { + expect(e.message).toEqual('User already linked'); + expect(e.status).toEqual(HttpStatus.UNPROCESSABLE_ENTITY); + } + }); + }); +}); diff --git a/src/temp-user/temp-user.service.ts b/src/temp-user/temp-user.service.ts index f4588a4b9c199c84ea49152a2d4ad3e29630faf1..8a43832237b1555687568f84cc92b2f877858336 100644 --- a/src/temp-user/temp-user.service.ts +++ b/src/temp-user/temp-user.service.ts @@ -1,4 +1,4 @@ -import { HttpException, HttpService, HttpStatus, Injectable } from '@nestjs/common'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { Model, Types } from 'mongoose'; import { MailerService } from '../mailer/mailer.service'; @@ -10,7 +10,6 @@ import { ITempUser } from './temp-user.interface'; @Injectable() export class TempUserService { constructor( - private readonly httpService: HttpService, private readonly mailerService: MailerService, @InjectModel(TempUser.name) private tempUserModel: Model<ITempUser> ) {} @@ -20,27 +19,26 @@ export class TempUserService { if (userInDb) { throw new HttpException('User already exists', HttpStatus.BAD_REQUEST); } - const createUser = new this.tempUserModel(createTempUser); + const createUser = await this.tempUserModel.create(createTempUser); // Send email this.sendUserMail(createUser, structureName); - createUser.save(); - return await this.findOne(createTempUser.email); + return this.findOne(createTempUser.email); } - public async findOne(mail: string): Promise<TempUser | undefined> { - return this.tempUserModel.findOne({ email: mail }).exec(); + public async findOne(mail: string): Promise<TempUser> { + return this.tempUserModel.findOne({ email: mail }); } - public async findById(id: string): Promise<TempUser | undefined> { - return this.tempUserModel.findById(Types.ObjectId(id)).exec(); + public async findById(id: string): Promise<TempUser> { + return this.tempUserModel.findById(Types.ObjectId(id)); } public async delete(mail: string): Promise<TempUser> { const userInDb = await this.findOne(mail); if (!userInDb) { - throw new HttpException('User already exists', HttpStatus.BAD_REQUEST); + throw new HttpException('User does not exists', HttpStatus.BAD_REQUEST); } - this.tempUserModel.deleteOne({ email: mail }).exec(); + this.tempUserModel.deleteOne({ email: mail }); return userInDb; } diff --git a/src/users/guards/isStructureOwner.guard.spec.ts b/src/users/guards/isStructureOwner.guard.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..a92ddcf4f77b41434d9956902f2816d87d9f5c23 --- /dev/null +++ b/src/users/guards/isStructureOwner.guard.spec.ts @@ -0,0 +1,103 @@ +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 { IsStructureOwnerGuard } from './isStructureOwner.guard'; +import { Types } from 'mongoose'; + +describe('isStrructureOwner', () => { + let guard: IsStructureOwnerGuard; + let reflector: Reflector; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [], + providers: [ + IsStructureOwnerGuard, + { + provide: Reflector, + useValue: { + constructor: jest.fn(), + get: jest.fn(), + }, + }, + ], + }).compile(); + + guard = module.get<IsStructureOwnerGuard>(IsStructureOwnerGuard); + 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: ['6001a38516b08100062e4161'], + structureOutdatedMailSent: [], + pendingStructuresLink: [], + 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: ['unIdQuiExistePasTropTrop'], + structureOutdatedMailSent: [], + pendingStructuresLink: [], + 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/roles.guard.spec.ts b/src/users/guards/roles.guard.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..2ef376e7c64a77fd3c15ab741758bf8f64a5c549 --- /dev/null +++ b/src/users/guards/roles.guard.spec.ts @@ -0,0 +1,95 @@ +import { Reflector } from '@nestjs/core'; +import { createMock } from '@golevelup/ts-jest'; +import { Test, TestingModule } from '@nestjs/testing'; +import { RolesGuard } from './roles.guard'; +import { ExecutionContext } from '@nestjs/common'; +import { UserRole } from '../enum/user-role.enum'; + +describe('RolesGuard', () => { + let guard: RolesGuard; + let reflector: Reflector; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [], + providers: [ + RolesGuard, + { + provide: Reflector, + useValue: { + constructor: jest.fn(), + get: jest.fn(), + }, + }, + ], + }).compile(); + + guard = module.get<RolesGuard>(RolesGuard); + reflector = module.get<Reflector>(Reflector); + }); + + afterEach(async () => { + jest.clearAllMocks(); + }); + + it('should be defined', () => { + expect(guard).toBeDefined(); + }); + + it('should skip(return true) if the `HasRoles` decorator is not set', async () => { + jest.spyOn(reflector, 'get').mockImplementation((a: any, b: any) => null); + const context = createMock<ExecutionContext>(); + const result = await guard.canActivate(context); + + expect(result).toBeTruthy(); + expect(reflector.get).toBeCalled(); + }); + + it('should return true if the `HasRoles` decorator and role is admin', async () => { + jest.spyOn(reflector, 'get').mockImplementation((a: any, b: any) => ['admin']); + const context = createMock<ExecutionContext>({ + getHandler: jest.fn(), + switchToHttp: jest.fn().mockReturnValue({ + getRequest: jest.fn().mockReturnValue({ + user: { role: UserRole.admin }, + }), + }), + }); + + const result = await guard.canActivate(context); + expect(result).toBeTruthy(); + expect(reflector.get).toBeCalled(); + }); + + it('should return false if the `HasRoles` decorator is set but role is not allowed', async () => { + jest.spyOn(reflector, 'get').mockImplementation((a: any, b: any) => ['admin']); + const context = createMock<ExecutionContext>({ + getHandler: jest.fn(), + switchToHttp: jest.fn().mockReturnValue({ + getRequest: jest.fn().mockReturnValue({ + user: { role: null }, + }), + }), + }); + + const result = await guard.canActivate(context); + expect(result).toBeFalsy(); + expect(reflector.get).toBeCalled(); + }); + + it('should return true if the `HasRoles` decorator is and role is not allowed', async () => { + jest.spyOn(reflector, 'get').mockImplementation((a: any, b: any) => ['user']); + const context = createMock<ExecutionContext>({ + getHandler: jest.fn(), + switchToHttp: jest.fn().mockReturnValue({ + getRequest: jest.fn().mockReturnValue({ + user: { role: UserRole.user }, + }), + }), + }); + + const result = await guard.canActivate(context); + expect(result).toBeTruthy(); + expect(reflector.get).toBeCalled(); + }); +}); diff --git a/src/users/users.controller.spec.ts b/src/users/users.controller.spec.ts index 68da5450f95300fde9828f6a2ad2d1905b738753..e65b3965aa24378b580c213e074b100d9e96d158 100644 --- a/src/users/users.controller.spec.ts +++ b/src/users/users.controller.spec.ts @@ -1,7 +1,6 @@ import { HttpModule } from '@nestjs/common'; import { getModelToken } from '@nestjs/mongoose'; import { Test, TestingModule } from '@nestjs/testing'; -import { CategoriesModule } from '../categories/categories.module'; import { ConfigurationModule } from '../configuration/configuration.module'; import { MailerService } from '../mailer/mailer.service'; import { SearchModule } from '../search/search.module'; diff --git a/test/mock/services/newsletter.mock.service.ts b/test/mock/services/newsletter.mock.service.ts index d7b4d640c0d9cd298961fe88a660aeebbb158b1d..6b4f0d47557ff8364078971221f44eccc0ff3405 100644 --- a/test/mock/services/newsletter.mock.service.ts +++ b/test/mock/services/newsletter.mock.service.ts @@ -44,7 +44,7 @@ export class NewsletterServiceMock { }); } - deleteOneEmail(email: string) { + newsletterUnsubscribe(email: string) { if (email === 'test@test.com') { throw new HttpException('Invalid email', HttpStatus.BAD_REQUEST); }