Skip to content

Commit

Permalink
Add date-fns, remove moment, add tests to safeguard change
Browse files Browse the repository at this point in the history
  • Loading branch information
eleanorreem committed Sep 12, 2023
1 parent 7ed3116 commit ba23597
Show file tree
Hide file tree
Showing 10 changed files with 124 additions and 47 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@
"axios": "^0.24.0",
"class-transformer": "^0.4.0",
"class-validator": "^0.14.0",
"date-fns": "^2.30.0",
"dotenv": "^10.0.0",
"firebase": "^9.4.1",
"firebase-admin": "^10.0.0",
"lodash": "^4.17.21",
"moment": "^2.29.4",
"passport": "^0.5.0",
"passport-firebase-jwt": "^1.2.1",
"pg": "^8.7.1",
Expand Down
75 changes: 46 additions & 29 deletions src/partner-access/partner-access.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { createMock, DeepMocked } from '@golevelup/ts-jest';
import { Test, TestingModule } from '@nestjs/testing';
import { sub } from 'date-fns';
import * as crispApi from 'src/api/crisp/crisp-api';
import { PartnerRepository } from 'src/partner/partner.repository';
import { GetUserDto } from 'src/user/dtos/get-user.dto';
import {
mockPartnerAccessEntity,
mockPartnerAccessEntityBase,
mockPartnerEntity,
mockUserEntity,
partnerAccessArray,
} from 'test/utils/mockData';
import { mockPartnerRepositoryMethods } from 'test/utils/mockedServices';
import {
mockPartnerAccessRepositoryMethods,
mockPartnerRepositoryMethods,
} from 'test/utils/mockedServices';
import { Repository } from 'typeorm';
import { createQueryBuilderMock } from '../../test/utils/mockUtils';
import { PartnerAccessEntity } from '../entities/partner-access.entity';
Expand All @@ -28,21 +32,6 @@ const createPartnerAccessDto: CreatePartnerAccessDto = {
therapySessionsRemaining: 5,
};

const partnerAccessEntityBase = {
id: 'randomId',
userId: null,
partnerId: '',
partnerAdminId: null,
user: null,
partnerAdmin: null,
partner: null,
active: false,
activatedAt: null,
accessCode: null,
createdAt: new Date(),
therapySession: [],
updatedAt: null,
};
const mockGetUserDto = {
user: mockUserEntity,
partnerAccesses: [],
Expand All @@ -62,26 +51,20 @@ describe('PartnerAccessService', () => {
let service: PartnerAccessService;
let repo: PartnerAccessRepository;
let mockPartnerRepository: DeepMocked<PartnerRepository>;
let mockPartnerAccessRepository: DeepMocked<PartnerAccessRepository>;

beforeEach(async () => {
mockPartnerRepository = createMock<PartnerRepository>(mockPartnerRepositoryMethods);
mockPartnerAccessRepository = createMock<PartnerAccessRepository>(
mockPartnerAccessRepositoryMethods,
);

const module: TestingModule = await Test.createTestingModule({
providers: [
PartnerAccessService,
{
provide: PartnerAccessRepository,
useFactory: jest.fn(() => ({
createQueryBuilder: createQueryBuilderMock(),
create: (dto: CreatePartnerAccessDto): PartnerAccessEntity | Error => {
return {
...partnerAccessEntityBase,
...dto,
};
},
find: jest.fn(() => partnerAccessArray),
save: jest.fn((arg) => arg),
})),
useValue: mockPartnerAccessRepository,
},
{
provide: PartnerRepository,
Expand All @@ -107,7 +90,7 @@ describe('PartnerAccessService', () => {
partnerId,
partnerAdminId,
);
const { accessCode, ...partnerEntityWithoutCode } = partnerAccessEntityBase;
const { accessCode, ...partnerEntityWithoutCode } = mockPartnerAccessEntityBase;
expect(generatedCode).toStrictEqual({
...partnerEntityWithoutCode,
...createPartnerAccessDto,
Expand Down Expand Up @@ -230,4 +213,38 @@ describe('PartnerAccessService', () => {
expect(partnerAccesses.length).toBe(1);
});
});

describe('getValidPartnerAccessCode', () => {
it('when a valid partner access is supplied, it should return partner access', async () => {
jest.spyOn(repo, 'createQueryBuilder').mockImplementationOnce(
createQueryBuilderMock({
leftJoinAndSelect: jest.fn().mockReturnThis(),
getOne: jest.fn().mockResolvedValue(mockPartnerAccessEntity),
}) as never,
);
const partnerAccess = await service.getValidPartnerAccessCode('123456');
expect(partnerAccess).toHaveProperty('accessCode', '123456');
});

it('when a valid partner access is supplied, but it was created over a year ago, it should throw error', async () => {
jest.spyOn(repo, 'createQueryBuilder').mockImplementationOnce(
createQueryBuilderMock({
leftJoinAndSelect: jest.fn().mockReturnThis(),

getOne: jest.fn().mockResolvedValue({
...mockPartnerAccessEntity,
createdAt: sub(new Date(), { years: 1, days: 1 }),
}),
}) as never,
);
await expect(service.getValidPartnerAccessCode('123456')).rejects.toThrowError(
'CODE_EXPIRED',
);
});
it('when an partner access with too many letters is supplied, it should throw error', async () => {
await expect(service.getValidPartnerAccessCode('1234567')).rejects.toThrowError(
'INVALID_CODE',
);
});
});
});
5 changes: 3 additions & 2 deletions src/partner-access/partner-access.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { isBefore, sub } from 'date-fns';
import _ from 'lodash';
import moment from 'moment';
import { PartnerEntity } from 'src/entities/partner.entity';
import { Logger } from 'src/logger/logger';
import { PartnerRepository } from 'src/partner/partner.repository';
Expand Down Expand Up @@ -81,7 +81,8 @@ export class PartnerAccessService {
throw new HttpException(PartnerAccessCodeStatusEnum.ALREADY_IN_USE, HttpStatus.CONFLICT);
}

if (moment(partnerAccess.createdAt).add(1, 'year').isSameOrBefore(Date.now())) {
// ensure the partner access code has been created no more than a year ago
if (isBefore(new Date(partnerAccess.createdAt), sub(new Date(), { years: 1 }))) {
throw new HttpException(PartnerAccessCodeStatusEnum.CODE_EXPIRED, HttpStatus.BAD_REQUEST);
}

Expand Down
25 changes: 25 additions & 0 deletions src/utils/serialize.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { mockSimplybookBodyBase } from 'test/utils/mockData';
import { formatTherapySessionObject } from './serialize';

describe('Serialize', () => {
describe('formatTherapySessionObject', () => {
it('should format object correctly when valid object is supplied', () => {
const randomString = formatTherapySessionObject(mockSimplybookBodyBase, 'partnerAccessId');
expect(randomString).toEqual({
action: 'UPDATED_BOOKING',
bookingCode: 'abc',
cancelledAt: null,
clientEmail: '[email protected]',
clientTimezone: 'Europe/London',
completedAt: null,
endDateTime: new Date('2022-09-12T08:30:00+0000'),
partnerAccessId: 'partnerAccessId',
rescheduledFrom: null,
serviceName: 'bloom therapy',
serviceProviderEmail: '[email protected]',
serviceProviderName: '[email protected]',
startDateTime: new Date('2022-09-12T07:30:00+0000'),
});
});
});
});
5 changes: 2 additions & 3 deletions src/utils/serialize.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import moment from 'moment';
import { PartnerEntity } from 'src/entities/partner.entity';
import { IPartnerFeature } from 'src/partner-feature/partner-feature.interface';
import { IPartner } from 'src/partner/partner.interface';
Expand Down Expand Up @@ -136,8 +135,8 @@ export const formatTherapySessionObject = (
serviceName: therapySession.service_name,
serviceProviderName: therapySession.service_provider_email,
serviceProviderEmail: therapySession.service_provider_email,
startDateTime: moment(therapySession.start_date_time).toDate(),
endDateTime: moment(therapySession.end_date_time).toDate(),
startDateTime: new Date(therapySession.start_date_time),
endDateTime: new Date(therapySession.end_date_time),
cancelledAt: null,
rescheduledFrom: null,
completedAt: null,
Expand Down
4 changes: 3 additions & 1 deletion src/webhooks/webhooks.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -391,14 +391,16 @@ describe('WebhooksService', () => {
describe('updatePartnerAccessTherapy', () => {
it('should update the booking time when action is update and time is different TODO ', async () => {
const newStartTime = '2022-09-12T09:30:00+0000';
const newEndTime = '2022-09-12T10:30:00+0000';
const therapyRepoFindOneSpy = jest.spyOn(mockedTherapySessionRepository, 'findOne');
const booking = await service.updatePartnerAccessTherapy({
...mockSimplybookBodyBase,
start_date_time: newStartTime,
end_date_time: '2022-09-12T010:30:00+0000',
end_date_time: newEndTime,
action: SIMPLYBOOK_ACTION_ENUM.UPDATED_BOOKING,
});
expect(booking).toHaveProperty('startDateTime', new Date(newStartTime));
expect(booking).toHaveProperty('endDateTime', new Date(newEndTime));
expect(therapyRepoFindOneSpy).toBeCalled();
});

Expand Down
5 changes: 2 additions & 3 deletions src/webhooks/webhooks.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import moment from 'moment';
import { MailchimpClient } from 'src/api/mailchimp/mailchip-api';
import { getBookingsForDate } from 'src/api/simplybook/simplybook-api';
import { SlackMessageClient } from 'src/api/slack/slack-api';
Expand Down Expand Up @@ -134,8 +133,8 @@ export class WebhooksService {
...(action === SIMPLYBOOK_ACTION_ENUM.UPDATED_BOOKING
? {
rescheduledFrom: therapySession.startDateTime,
startDateTime: moment(simplyBookDto.start_date_time).toDate(),
endDateTime: moment(simplyBookDto.end_date_time).toDate(),
startDateTime: new Date(simplyBookDto.start_date_time),
endDateTime: new Date(simplyBookDto.end_date_time),
}
: {}),
...(action === SIMPLYBOOK_ACTION_ENUM.COMPLETED_BOOKING ? { completedAt: new Date() } : {}),
Expand Down
15 changes: 15 additions & 0 deletions test/utils/mockData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,21 @@ export const mockPartnerEntity = {
partnerFeature: [],
} as PartnerEntity;

export const mockPartnerAccessEntityBase = {
id: 'randomId',
userId: null,
partnerId: '',
partnerAdminId: null,
user: null,
partnerAdmin: null,
partner: null,
active: false,
activatedAt: null,
accessCode: null,
createdAt: new Date(),
therapySession: [],
updatedAt: null,
};
export const mockPartnerAccessEntity = {
id: 'pa1',
therapySessionsRemaining: 5,
Expand Down
11 changes: 8 additions & 3 deletions test/utils/mockedServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ import {
mockEmailCampaignEntity,
mockFeatureEntity,
mockPartnerAccessEntity,
mockPartnerAccessEntityBase,
mockPartnerEntity,
mockPartnerFeatureEntity,
mockSession,
mockTherapySessionEntity,
mockUserEntity,
mockUserRecord,
partnerAccessArray,
} from './mockData';
import { createQueryBuilderMock } from './mockUtils';

Expand Down Expand Up @@ -111,15 +113,18 @@ export const mockPartnerAccessRepositoryMethods: PartialFuncReturn<PartnerAccess
createQueryBuilder: createQueryBuilderMock(),
create: (dto) => {
return {
...mockPartnerAccessEntity,
...mockPartnerAccessEntityBase,
...dto,
} as PartnerAccessEntity;
};
},
findOne: async (arg) => {
return { ...mockPartnerAccessEntity, ...(arg ? { ...arg } : {}) } as PartnerAccessEntity;
},
find: async (arg) => {
return [{ ...mockPartnerAccessEntity, ...(arg ? { ...arg } : {}) }] as PartnerAccessEntity[];
return [
...partnerAccessArray,
{ ...mockPartnerAccessEntity, ...(arg ? { ...arg } : {}) },
] as PartnerAccessEntity[];
},
save: async (arg) => arg as PartnerAccessEntity,
};
Expand Down
24 changes: 19 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.14.5"

"@babel/runtime@^7.21.0":
version "7.22.15"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.15.tgz#38f46494ccf6cf020bd4eed7124b425e83e523b8"
integrity sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==
dependencies:
regenerator-runtime "^0.14.0"

"@babel/template@^7.15.4", "@babel/template@^7.3.3":
version "7.15.4"
resolved "https://registry.npmjs.org/@babel/template/-/template-7.15.4.tgz"
Expand Down Expand Up @@ -2672,6 +2679,13 @@ date-and-time@^2.0.0:
resolved "https://registry.yarnpkg.com/date-and-time/-/date-and-time-2.0.1.tgz#bc8b72704980e8a0979bb186118d30d02059ef04"
integrity sha512-O7Xe5dLaqvY/aF/MFWArsAM1J4j7w1CSZlPCX9uHgmb+6SbkPd8Q4YOvfvH/cZGvFlJFfHOZKxQtmMUOoZhc/w==

date-fns@^2.30.0:
version "2.30.0"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0"
integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==
dependencies:
"@babel/runtime" "^7.21.0"

[email protected]:
version "2.6.9"
resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz"
Expand Down Expand Up @@ -4949,11 +4963,6 @@ mkdirp@^1.0.4:
resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==

moment@^2.29.4:
version "2.29.4"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108"
integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==

[email protected]:
version "2.0.0"
resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz"
Expand Down Expand Up @@ -5639,6 +5648,11 @@ reflect-metadata@^0.1.13:
resolved "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz"
integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==

regenerator-runtime@^0.14.0:
version "0.14.0"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45"
integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==

regexpp@^3.1.0:
version "3.2.0"
resolved "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz"
Expand Down

0 comments on commit ba23597

Please sign in to comment.