Skip to content

Commit

Permalink
complete crisp refactor + fix tests
Browse files Browse the repository at this point in the history
  • Loading branch information
annarhughes committed May 28, 2024
1 parent 0fd9c2d commit c689174
Show file tree
Hide file tree
Showing 9 changed files with 82 additions and 113 deletions.
45 changes: 7 additions & 38 deletions src/api/crisp/crisp-api.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,67 +16,36 @@ describe('CrispApi', () => {
async () =>
({
data: { error: false, reason: 'resolved', data: {} },
} as AxiosResponse<CrispResponse>),
}) as AxiosResponse<CrispResponse>,
)
.mockImplementationOnce(
async () =>
({
data: { error: false, reason: 'resolved', data: {} },
} as AxiosResponse<CrispResponse>),
}) as AxiosResponse<CrispResponse>,
)
.mockImplementationOnce(
async () =>
({
data: { error: false, reason: 'resolved', data: { segments: ['public'] } },
} as AxiosResponse<CrispProfileResponse>),
}) as AxiosResponse<CrispProfileResponse>,
);

// Clear the mock so the next test starts with fresh data

await updateCrispProfileAccesses(
mockUserEntity,
[{ ...mockPartnerAccessEntity, partner: mockPartnerEntity }],
[],
);
await updateCrispProfileAccesses(mockUserEntity, [
{ ...mockPartnerAccessEntity, partner: mockPartnerEntity },
]);
const baseUrl = `https://api.crisp.chat/v1/website/${crispWebsiteId}`;
// request to get profile data
expect(apiCall).toHaveBeenNthCalledWith(
1,
expect.objectContaining({
url: `${baseUrl}/people/data/${mockUserEntity.email}`,
type: 'get',
}),
);

expect(apiCall).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
// updating people data
url: `${baseUrl}/people/data/${mockUserEntity.email}`,
type: 'patch',
}),
);
expect(apiCall).toHaveBeenNthCalledWith(
3,
// request to get
expect.objectContaining({
// request to update profile
url: `${baseUrl}/people/profile/${mockUserEntity.email}`,
type: 'get',
}),
);
expect(apiCall).toHaveBeenNthCalledWith(
4,
// request to get
expect.objectContaining({
// request to update profile
url: `${baseUrl}/people/profile/${mockUserEntity.email}`,
type: 'patch',
data: expect.objectContaining({
segments: ['bumble', 'public'],
}),
}),
);
expect(apiCall).toHaveBeenCalledTimes(1);
mockedApiCall.mockClear();
});
});
Expand Down
18 changes: 18 additions & 0 deletions src/api/crisp/crisp-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,24 @@ const headers = {

const logger = new Logger('UserService');

export const updateCrispProfileTherapy = async (partnerAccesses: PartnerAccessEntity[], email) => {
const therapySessionsRemaining = partnerAccesses.reduce(
(sum, partnerAccess) => sum + partnerAccess.therapySessionsRemaining,
0,
);
const therapySessionsRedeemed = partnerAccesses.reduce(
(sum, partnerAccess) => sum + partnerAccess.therapySessionsRedeemed,
0,
);

const therapyData = {
therapy_sessions_remaining: therapySessionsRemaining,
therapy_sessions_redeemed: therapySessionsRedeemed,
};

updateCrispProfileData(therapyData, email);
};

export const updateCrispProfileAccesses = async (
user: UserEntity,
partnerAccesses: PartnerAccessEntity[],
Expand Down
24 changes: 11 additions & 13 deletions src/partner-access/partner-access.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,42 +130,40 @@ describe('PartnerAccessService', () => {
jest.spyOn(repo, 'save').mockImplementationOnce(async () => {
return {
...mockPartnerAccessEntity,
id: '123456',
id: 'pa1',
userId: mockGetUserDto.user.id,
};
});
// Mocks that the accesscode already exists
jest.spyOn(repo, 'findOne').mockResolvedValueOnce(mockPartnerAccessEntity);

const partnerAccess = await service.assignPartnerAccess(mockGetUserDto, '123456');
const partnerAccess = await service.assignPartnerAccess(mockUserEntity, '123456');

expect(partnerAccess).toEqual({
...mockPartnerAccessEntity,
id: 'pa1',
userId: mockGetUserDto.user.id,
userId: mockUserEntity.id,
activatedAt: partnerAccess.activatedAt,
});

expect(crispApi.updateCrispProfileAccesses).toHaveBeenCalledWith(
mockGetUserDto.user,
[partnerAccess],
[],
);
expect(crispApi.updateCrispProfileAccesses).toHaveBeenCalledWith(mockUserEntity, [
partnerAccess,
]);
});

it('should assign partner access even if crisp profile api fails', async () => {
// Mocks that the accesscode already exists
jest.spyOn(repo, 'findOne').mockResolvedValueOnce(mockPartnerAccessEntity);

jest.spyOn(crispApi, 'updateCrispProfileAccesses').mockImplementationOnce(async () => {
jest.spyOn(crispApi, 'updateCrispProfileData').mockImplementationOnce(async () => {
throw new Error('Test throw');
});

const partnerAccess = await service.assignPartnerAccess(mockGetUserDto, '123456');
const partnerAccess = await service.assignPartnerAccess(mockUserEntity, '123456');

expect(partnerAccess).toEqual({
...mockPartnerAccessEntity,
userId: mockGetUserDto.user.id,
userId: mockUserEntity.id,
activatedAt: partnerAccess.activatedAt,
});
});
Expand All @@ -177,7 +175,7 @@ describe('PartnerAccessService', () => {
userId: 'anotherUserId',
});

await expect(service.assignPartnerAccess(mockGetUserDto, '123456')).rejects.toThrow(
await expect(service.assignPartnerAccess(mockUserEntity, '123456')).rejects.toThrow(
PartnerAccessCodeStatusEnum.ALREADY_IN_USE,
);
});
Expand All @@ -189,7 +187,7 @@ describe('PartnerAccessService', () => {
userId: mockGetUserDto.user.id,
});

await expect(service.assignPartnerAccess(mockGetUserDto, '123456')).rejects.toThrow(
await expect(service.assignPartnerAccess(mockUserEntity, '123456')).rejects.toThrow(
PartnerAccessCodeStatusEnum.ALREADY_APPLIED,
);
});
Expand Down
7 changes: 6 additions & 1 deletion src/partner-access/partner-access.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,9 +184,14 @@ export class PartnerAccessService {
userId: user.id,
activatedAt: new Date(),
});
assignedPartnerAccess.partner = partnerAccess.partner;

try {
await updateCrispProfileAccesses(user, [...user.partnerAccess, assignedPartnerAccess]);
const partnerAccesses = await this.partnerAccessRepository.findBy({
userId: user.id,
active: true,
});
updateCrispProfileAccesses(user, partnerAccesses);
} catch (error) {
this.logger.error(
`Error: Unable to update crisp profile for ${user.email}. Error: ${error.message} `,
Expand Down
11 changes: 7 additions & 4 deletions src/user/user.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ describe('UserService', () => {

const user = await service.createUser({
...createUserDto,
partnerId: mockPartnerEntity.id,
partnerAccessCode: mockPartnerAccessEntity.accessCode,
});

Expand Down Expand Up @@ -208,10 +209,12 @@ describe('UserService', () => {
}) as never,
);

const user = await service.getUserByFirebaseId(mockIFirebaseUser);
expect(user.user.email).toBe('user@email.com');
expect(user.partnerAdmin).toBeNull();
expect(user.partnerAccesses).toEqual([]);
const userResponse = await service.getUserByFirebaseId(mockIFirebaseUser);
expect(userResponse.userEntity.email).toBe('user@email.com');
expect(userResponse.userDto.user.email).toBe('user@email.com');
expect(userResponse.userDto.user.email).toBe('user@email.com');
expect(userResponse.userDto.partnerAdmin).toBeNull();
expect(userResponse.userDto.partnerAccesses).toEqual([]);
});
});

Expand Down
8 changes: 3 additions & 5 deletions src/user/user.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ export class UserService {
? SIGNUP_TYPE.PARTNER_USER_WITHOUT_CODE
: SIGNUP_TYPE.PUBLIC_USER;

let partnerAccess: PartnerAccessEntity | null;
const partner = await this.partnerRepository.findOneBy({ id: partnerId });
let partnerAccess: PartnerAccessEntity | null = null;
const partner = partnerId ? await this.partnerRepository.findOneBy({ id: partnerId }) : null;

try {
if (signUpType === SIGNUP_TYPE.PARTNER_USER_WITHOUT_CODE) {
Expand Down Expand Up @@ -93,9 +93,7 @@ export class UserService {
}
}

public async getUserByFirebaseId({
uid,
}: IFirebaseUser): Promise<{
public async getUserByFirebaseId({ uid }: IFirebaseUser): Promise<{
userEntity: UserEntity | undefined;
userDto: GetUserDto | undefined;
}> {
Expand Down
41 changes: 16 additions & 25 deletions src/utils/profileData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { addCrispProfile, updateCrispProfileData } from 'src/api/crisp/crisp-api
import { CourseUserEntity } from 'src/entities/course-user.entity';
import { PartnerAccessEntity } from 'src/entities/partner-access.entity';
import { PartnerEntity } from 'src/entities/partner.entity';
import { SessionUserEntity } from 'src/entities/session-user.entity';
import { UserEntity } from 'src/entities/user.entity';
import { PROGRESS_STATUS } from './constants';

export const getAcronym = (text: string) => {
return `${text
Expand All @@ -12,22 +12,11 @@ export const getAcronym = (text: string) => {
.toLowerCase()}`;
};

export const formatCourseUserValue = (courseUser: CourseUserEntity) => {
return `${getAcronym(courseUser.course.name)}:${courseUser.completed ? 'C' : 'S'}`;
};

export const formatSessionUserValue = (sessionUser: SessionUserEntity) => {
return `${getAcronym(sessionUser.session.name)}:${sessionUser.completed ? 'C' : 'S'}`;
};

export const createServicesProfiles = async (
user: UserEntity,
partner: PartnerEntity,
partnerAccess: PartnerAccessEntity,
courses?: CourseUserEntity[],
) => {
if (partnerAccess) partnerAccess.partner = partner;

await addCrispProfile({
email: user.email,
person: { nickname: user.name },
Expand All @@ -37,8 +26,7 @@ export const createServicesProfiles = async (
await updateCrispProfileData(
{
...serializeUserData(user),
...serializePartnerAccessData([partnerAccess]),
...(courses?.length && { ...serializeCourseData(courses) }),
...(partnerAccess && serializePartnerAccessData([{ ...partnerAccess, partner }])),
},
user.email,
);
Expand All @@ -55,7 +43,7 @@ export const serializeUserData = (user: UserEntity) => {

export const serializePartnerAccessData = (partnerAccesses: PartnerAccessEntity[]) => {
const partnerAccessData = {
partners: partnerAccesses.map((pa) => pa.partner.name).join('; ') || '',
partners: partnerAccesses.map((pa) => pa.partner?.name || '').join('; ') || '',
feature_live_chat: !!partnerAccesses.find((pa) => !!pa.featureLiveChat),
feature_therapy: !!partnerAccesses.find((pa) => !!pa.featureTherapy),
therapy_sessions_remaining: partnerAccesses
Expand All @@ -65,20 +53,23 @@ export const serializePartnerAccessData = (partnerAccesses: PartnerAccessEntity[
.map((pa) => pa.therapySessionsRedeemed)
.reduce((a, b) => a + b, 0),
};

return partnerAccessData;
};

export const serializeCourseData = (courseUsers?: CourseUserEntity[]) => {
const sessionKeyValues = {};

courseUsers.forEach((courseUser) => {
sessionKeyValues[`course_${getAcronym(courseUser.course.name)}_sessions`] =
courseUser.sessionUser.map((sessionUser) => formatSessionUserValue(sessionUser)).join('; ');
});

export const serializeCourseData = (courseUser: CourseUserEntity) => {
// Returns e.g. { course_IBA: "Started", course_IBA_sessions: "IBDP:Started; DOC:Completed"}
const courseData = {
courses: courseUsers.map((courseUser) => formatCourseUserValue(courseUser)).join(';'),
...sessionKeyValues,
[`course_${getAcronym(courseUser.course.name)}`]: courseUser.completed
? PROGRESS_STATUS.COMPLETED
: PROGRESS_STATUS.STARTED,

[`course_${getAcronym(courseUser.course.name)}_sessions`]: courseUser.sessionUser
.map(
(sessionUser) =>
`${getAcronym(sessionUser.session.name)}:${sessionUser.completed ? 'C' : 'S'}`,
)
.join('; '),
};

return courseData;
Expand Down
3 changes: 3 additions & 0 deletions src/webhooks/webhooks.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ jest.mock('../api/crisp/crisp-api', () => {
updateCrispProfileData: () => {
return;
},
updateCrispProfileTherapy: () => {
return;
},
getCrispPeopleData: () => {
return {
error: false,
Expand Down
Loading

0 comments on commit c689174

Please sign in to comment.