import { TestingModule, Test } from '@nestjs/testing' import { addDays, format, subDays } from 'date-fns' import { date, datatype, random } from 'faker' import { cloneDeep, sample } from 'lodash' import { Appointment, Roles, User } from '../../../models' import { AppointmentRepository, AvailabilityRepository, AvailabilityTimeRepository, COUNT_APPOINTMENTS, COUNT_APPOINTMENTS_TYPES, COUNT_AVAILABLE_TIMES, MessageRepository, UserRepository, TOTAL_RATINGS, } from '../../../repositories' import { factory } from '../../../../test/factories' import { MockedModule } from '../../../../test/types' import { parseISO } from '../../../utils/date' import { TransferService } from '../../../services/bookings/transfer.service' import { AVG_CONSULTATION_DURATION } from '../../../../test/factories/appointment.factory' import { Booking } from './booking' import { APPOINTMENT_TYPES } from './types' describe('Booking', () => { let subject: Booking let appointmentRepository: MockedModule let availabilityRepository: MockedModule let availabilityTimeRepository: MockedModule let messageRepository: MockedModule let transferService: MockedModule let userRepository: MockedModule let totalRatingsResult: TOTAL_RATINGS let countAvailableTimes: COUNT_AVAILABLE_TIMES let appointmentsDelayedCount: number let appointmentsWithDuration: Appointment[] let countAppointmentsTypes: COUNT_APPOINTMENTS_TYPES let totalCountAppointmentsResult: COUNT_APPOINTMENTS const countAppointmentsResult: COUNT_APPOINTMENTS = {} const roles = [datatype.uuid()] const analytical = {} const endDate = new Date() const beginDate = subDays(endDate, 7) beforeAll(async () => { appointmentRepository = { getGeneralAppointmentsCount: jest.fn(), getGeneralTotalRatings: jest.fn(), getCountOfAppointmentsWithDelayedPatient: jest.fn(), getGeneralTotalAppointmentsByType: jest.fn(), getUserAppointmentsCount: jest.fn(), getUserTotalRatings: jest.fn(), getUserCountOfAppointmentsWithDelayedPatient: jest.fn(), getUserTotalAppointmentsByType: jest.fn(), findDelayedsFoodPlan: jest.fn(), findUserAppointmentsWithDuration: jest.fn(), getGeneralAppointmentsWithDuration: jest.fn(), findUserAppointments: jest.fn(), save: jest.fn(), } availabilityRepository = { findScheduledAvailabilityDates: jest.fn(), } availabilityTimeRepository = { blockAllAvailabilityTimes: jest.fn(), getGeneralAvailability: jest.fn(), getUserAvailability: jest.fn(), getByAppointmentId: jest.fn(), get: jest.fn(), update: jest.fn(), } messageRepository = { getUserPendingMessagesCount: jest.fn(), transferPatientPendingMessages: jest.fn(), } transferService = { transfer: jest.fn(), } userRepository = { get: jest.fn(), getRole: jest.fn(), } const syntactic = [] let checkDate = new Date(beginDate.getTime()) const keys = ['scheduled', 'performed', 'cancelled_by_patient', 'cancelled_by_nutri'] while (checkDate <= endDate) { const startOfDayTimestamp = checkDate.getTime() const scheduled = datatype.number({ min: 0, max: 99 }) const performed = datatype.number({ min: 0, max: 99 }) const cancelled_by_patient = datatype.number({ min: 0, max: 99 }) const cancelled_by_nutri = datatype.number({ min: 0, max: 99 }) countAppointmentsResult[`scheduled_${startOfDayTimestamp}`] = scheduled countAppointmentsResult[`performed_${startOfDayTimestamp}`] = performed countAppointmentsResult[`cancelled_by_patient_${startOfDayTimestamp}`] = cancelled_by_patient countAppointmentsResult[`cancelled_by_nutri_${startOfDayTimestamp}`] = cancelled_by_nutri analytical[format(checkDate, 'yyyy-MM-dd')] = { scheduled, performed, cancelled_by_patient, cancelled_by_nutri, } syntactic.push( keys.reduce( (previous, current) => ({ ...previous, [current]: countAppointmentsResult[`${current}_${startOfDayTimestamp}`], }), {}, ), ) checkDate = addDays(checkDate, 1) } totalCountAppointmentsResult = syntactic.reduce( (syntactic: COUNT_APPOINTMENTS, value: COUNT_APPOINTMENTS) => Object.keys(syntactic).reduce( (result, key) => ({ ...result, [key]: syntactic[key] + value[key] }), syntactic, ), keys.reduce((previous, current) => ({ ...previous, [current]: 0 }), {}), ) totalRatingsResult = { total: datatype.number({ min: 0, max: 99 }), average: datatype.number({ min: 0, max: 99 }), } countAvailableTimes = { available: datatype.number({ min: 0, max: 99 }), } appointmentsDelayedCount = datatype.number({ min: 0, max: 10 }) appointmentsWithDuration = await factory.buildMany('appointment', 5) countAppointmentsTypes = { first: datatype.number({ min: 0, max: 99 }), return: datatype.number({ min: 0, max: 99 }), } const app: TestingModule = await Test.createTestingModule({ controllers: [], providers: [ { provide: AppointmentRepository, useValue: appointmentRepository, }, { provide: AvailabilityRepository, useValue: availabilityRepository, }, { provide: AvailabilityTimeRepository, useValue: availabilityTimeRepository, }, { provide: MessageRepository, useValue: messageRepository, }, { provide: TransferService, useValue: transferService, }, { provide: UserRepository, useValue: userRepository, }, Booking, ], }).compile() subject = app.get(Booking) }) beforeEach(() => { appointmentRepository.getGeneralAppointmentsCount.mockResolvedValue(countAppointmentsResult) appointmentRepository.getGeneralTotalRatings.mockResolvedValue(totalRatingsResult) availabilityTimeRepository.getGeneralAvailability.mockResolvedValue(countAvailableTimes) appointmentRepository.getCountOfAppointmentsWithDelayedPatient.mockResolvedValue(appointmentsDelayedCount) appointmentRepository.getGeneralTotalAppointmentsByType.mockResolvedValue(countAppointmentsTypes) appointmentRepository.getUserAppointmentsCount.mockResolvedValue(countAppointmentsResult) appointmentRepository.getUserTotalRatings.mockResolvedValue(totalRatingsResult) availabilityTimeRepository.getUserAvailability.mockResolvedValue(countAvailableTimes) appointmentRepository.getUserCountOfAppointmentsWithDelayedPatient.mockResolvedValue(appointmentsDelayedCount) appointmentRepository.getUserTotalAppointmentsByType.mockResolvedValue(countAppointmentsTypes) appointmentRepository.findUserAppointmentsWithDuration.mockResolvedValue(appointmentsWithDuration) appointmentRepository.getGeneralAppointmentsWithDuration.mockResolvedValue(appointmentsWithDuration) }) describe('#makeGeneralDataByRole', () => { const countryCode = sample(['pt_BR', 'es_MX']) it('call getGeneralAppointmentsCount', async () => { await subject['makeGeneralDataByRole'](beginDate, endDate, countryCode, roles) expect(appointmentRepository.getGeneralAppointmentsCount).toBeCalledWith(beginDate, endDate, countryCode, roles) }) it('call getGeneralTotalRatings', async () => { await subject['makeGeneralDataByRole'](beginDate, endDate, countryCode, roles) expect(appointmentRepository.getGeneralTotalRatings).toBeCalledWith(beginDate, endDate, countryCode, roles) }) it('call getGeneralAvailability', async () => { await subject['makeGeneralDataByRole'](beginDate, endDate, countryCode, roles) expect(availabilityTimeRepository.getGeneralAvailability).toBeCalledWith(beginDate, endDate, countryCode, roles) }) it('call getGeneralAppointmentsWithDelayedUser', async () => { await subject['makeGeneralDataByRole'](beginDate, endDate, countryCode, roles) expect(appointmentRepository.getCountOfAppointmentsWithDelayedPatient).toBeCalledWith( beginDate, endDate, countryCode, roles, ) }) it('call getGeneralTotalAppointmentsByType', async () => { await subject['makeGeneralDataByRole'](beginDate, endDate, countryCode, roles) expect(appointmentRepository.getGeneralTotalAppointmentsByType).toBeCalledWith( beginDate, endDate, countryCode, roles, ) }) it('returns complete report data', async () => { await expect(subject['makeGeneralDataByRole'](beginDate, endDate, countryCode, roles)).resolves.toEqual({ appointments: { syntactic: totalCountAppointmentsResult, analytical, }, consultation_average_duration: AVG_CONSULTATION_DURATION, ratings: totalRatingsResult, available_times: countAvailableTimes, appointments_delayed: appointmentsDelayedCount, appointments_by_type: countAppointmentsTypes, }) }) }) describe('#generateGeneralData', () => { let report = {} const countryCode = sample(['pt_BR', 'es_MX']) const appointmentType = sample(APPOINTMENT_TYPES) beforeAll(() => { report = { appointments: { syntactic: totalCountAppointmentsResult, analytical, }, consultation_average_duration: AVG_CONSULTATION_DURATION, ratings: totalRatingsResult, available_times: countAvailableTimes, appointments_delayed: appointmentsDelayedCount, appointments_by_type: countAppointmentsTypes, } }) it(`returns ${appointmentType} report`, async () => { await expect(subject.generateGeneralData({ beginDate, endDate, countryCode, appointmentType })).resolves.toEqual({ [appointmentType]: report, }) }) }) describe('#generateNutritionistData', () => { const nutritionistId = datatype.uuid() const pendingMessagesCount = datatype.number({ min: 0 }) let delayedsFoodPlan: Appointment[] let appointmentsFinished: Appointment[] const role = sample(Object.values(Roles)) beforeAll(async () => { delayedsFoodPlan = [await factory.build('appointment:food_plan_pending')] appointmentsFinished = [ await factory.build('appointment:finished', { patient: { name: random.words() }, feedback: random.words() }), ] }) beforeEach(() => { userRepository.getRole.mockResolvedValue(role) messageRepository.getUserPendingMessagesCount.mockResolvedValue(pendingMessagesCount) appointmentRepository.findDelayedsFoodPlan.mockResolvedValue(delayedsFoodPlan) appointmentRepository.findUserAppointmentsWithDuration.mockResolvedValue(appointmentsFinished) }) it('call getUserAppointmentsCount', async () => { await subject.generateNutritionistData({ beginDate, endDate, nutritionistId }) expect(appointmentRepository.getUserAppointmentsCount).toBeCalledWith(beginDate, endDate, nutritionistId) }) it('call getUserTotalRatings', async () => { await subject.generateNutritionistData({ beginDate, endDate, nutritionistId }) expect(appointmentRepository.getUserTotalRatings).toBeCalledWith(beginDate, endDate, nutritionistId) }) it('call getUserAvailability', async () => { await subject.generateNutritionistData({ beginDate, endDate, nutritionistId }) expect(availabilityTimeRepository.getUserAvailability).toBeCalledWith(beginDate, endDate, nutritionistId) }) it('call getUserCountOfAppointmentsWithDelayedPatient', async () => { await subject.generateNutritionistData({ beginDate, endDate, nutritionistId }) expect(appointmentRepository.getUserCountOfAppointmentsWithDelayedPatient).toBeCalledWith( beginDate, endDate, nutritionistId, ) }) it('call getUserTotalAppointmentsByType', async () => { await subject.generateNutritionistData({ beginDate, endDate, nutritionistId }) expect(appointmentRepository.getUserTotalAppointmentsByType).toBeCalledWith(beginDate, endDate, nutritionistId) }) it('call getUserPendingMessagesCount', async () => { await subject.generateNutritionistData({ beginDate, endDate, nutritionistId }) expect(messageRepository.getUserPendingMessagesCount).toBeCalledWith(beginDate, endDate, nutritionistId) }) it('returns complete report data', async () => { await expect(subject.generateNutritionistData({ beginDate, endDate, nutritionistId })).resolves.toEqual({ [subject['getAppointmentTypeByRole'](role)]: { appointments: { syntactic: totalCountAppointmentsResult, analytical, }, ratings: { ...totalRatingsResult, feedback_rating: (totalRatingsResult.total * 100) / totalCountAppointmentsResult.performed, }, consultation_average_duration: AVG_CONSULTATION_DURATION, available_times: countAvailableTimes, appointments_delayed: appointmentsDelayedCount, appointments_by_type: countAppointmentsTypes, pending_chat: pendingMessagesCount, delayed_food_plan: delayedsFoodPlan.length, comments: appointmentsFinished.map(appointment => ({ name: appointment.patient.name, message: appointment.feedback, rating: appointment.rating, })), }, }) }) }) describe('#transferUserAppointments', () => { const userId = datatype.uuid() const date = new Date() const appointments: Appointment[] = [] let user: User beforeAll(async () => { user = await factory.build('user', { id: userId }) }) beforeEach(() => { appointmentRepository.findUserAppointments.mockResolvedValue(cloneDeep(appointments)) userRepository.get.mockResolvedValue(cloneDeep(user)) }) afterEach(() => { jest.resetAllMocks() }) describe('when does not have appointments', () => { it('does not find similar users', async () => { await subject.transferUserAppointments(userId, date) expect(transferService.transfer).not.toBeCalled() }) }) describe('when has appointments', () => { beforeAll(async () => { appointments.push( await factory.build('appointment', { scheduled_to: parseISO(`${format(date, 'yyyy-MM-dd')}T10:00:00`) }), ) appointments.push( await factory.build('appointment', { scheduled_to: parseISO(`${format(date, 'yyyy-MM-dd')}T11:30:00`) }), ) }) it('block all availability_times from user', async () => { await subject.transferUserAppointments(userId, date) expect(availabilityTimeRepository.blockAllAvailabilityTimes).toBeCalledWith(user.id, date) }) it('transfer all appointments', async () => { await subject.transferUserAppointments(userId, date) expect(transferService.transfer).toBeCalledTimes(appointments.length) }) }) }) describe('#transferAllUserAppointments', () => { const dates: Date[] = [] const userId = datatype.uuid() let user: User beforeAll(async () => { dates.push(date.future()) dates.push(date.past()) dates.push(date.recent()) user = await factory.build('user') }) beforeEach(() => { availabilityRepository.findScheduledAvailabilityDates.mockResolvedValue(dates) appointmentRepository.findUserAppointments.mockResolvedValue([]) userRepository.get.mockResolvedValue(cloneDeep(user)) }) it('transfers all found dates', async () => { await subject.transferAllUserAppointments(userId) expect(appointmentRepository.findUserAppointments).toBeCalledTimes(dates.length) }) }) })