import { TestingModule, Test } from '@nestjs/testing' import { datatype } from 'faker' import MockDate from 'mockdate' import { Appointment, Patient, User } from '../../../models' import { AppointmentRepository } from '../../../repositories/appointment.repository' import { factory } from '../../../../test/factories' import { MockedModule } from '../../../../test/types' import { BusinessExceptionError, NotFoundError, RequiredArgumentError } from '../../../errors' import { Vonage } from '../../../services/vonage' import { VideoCall } from './video_call' describe('VideoCall', () => { let subject: VideoCall let appointmentRepository: MockedModule let vonage: MockedModule beforeAll(async () => { appointmentRepository = { get: jest.fn(), save: jest.fn(), } vonage = { createSession: jest.fn(), generateToken: jest.fn(), } const app: TestingModule = await Test.createTestingModule({ controllers: [], providers: [ { provide: AppointmentRepository, useValue: appointmentRepository, }, { provide: Vonage, useValue: vonage, }, VideoCall, ], }).compile() subject = app.get(VideoCall) }) afterEach(() => { jest.resetAllMocks() }) describe('#getAppointment', () => { let appointmentId: string beforeAll(() => { appointmentId = datatype.uuid() }) describe('when appointment is not found', () => { beforeEach(() => { appointmentRepository.get.mockResolvedValue(null) }) it('throws error', async () => { await expect(subject['getAppointment'](appointmentId)).rejects.toThrowError(NotFoundError) }) }) describe('when appointment is found', () => { let appointment: Appointment beforeAll(async () => { appointment = await factory.build('appointment') }) beforeEach(async () => { appointmentRepository.get.mockResolvedValue(appointment) }) it('returns appointment', async () => { await expect(subject['getAppointment'](appointmentId)).resolves.toEqual(appointment) }) }) }) describe('#registerPatientArrived', () => { let appointment: Appointment beforeEach(async () => { appointment = await factory.build('appointment') }) describe('when appointment current state is not "user_waiting"', () => { it('saves appointment\'s state with "patient_waiting"', async () => { await subject['registerPatientArrived'](appointment) expect(appointmentRepository.save).toBeCalledWith( expect.objectContaining({ id: appointment.id, state: 'patient_waiting', patient_arrived_at: expect.any(Date), }), ['state', 'patient_arrived_at'], ) }) }) describe('when appointment current state is "user_waiting"', () => { beforeEach(() => { appointment.state = 'user_waiting' }) it('saves appointment\'s state with "in_consultation"', async () => { await subject['registerPatientArrived'](appointment) expect(appointmentRepository.save).toBeCalledWith( expect.objectContaining({ id: appointment.id, state: 'in_consultation', patient_arrived_at: expect.any(Date), }), ['state', 'patient_arrived_at'], ) }) }) }) describe('#generateSession', () => { let appointment: Appointment let now: Date beforeAll(() => { now = new Date() MockDate.set(now) }) beforeEach(async () => { appointment = await factory.build('appointment') }) it("creates vonage's room session", async () => { await subject['generateSession'](appointment) expect(vonage.createSession).toBeCalled() }) it("creates vonage's user and patient tokens", async () => { await subject['generateSession'](appointment) expect(vonage.generateToken).toBeCalledTimes(2) }) describe('when patient has not yet arrived', () => { beforeEach(() => { appointment.state = 'scheduled' }) it('updates state to "user_waiting"', async () => { await subject['generateSession'](appointment) expect(appointmentRepository.save.mock.calls[0][0]).toEqual( expect.objectContaining({ id: appointment.id, state: 'user_waiting' }), ) }) }) describe('when patient arrived', () => { beforeEach(() => { appointment.state = 'patient_waiting' }) it('updates state to "in_consultation"', async () => { await subject['generateSession'](appointment) expect(appointmentRepository.save.mock.calls[0][0]).toEqual( expect.objectContaining({ id: appointment.id, state: 'in_consultation' }), ) }) }) }) describe('#enterVideocall', () => { let requester: Patient | User let appointmentId: string beforeAll(() => { appointmentId = datatype.uuid() }) describe('when requester is not given', () => { it('throws error', async () => { await expect(subject.enterVideocall(requester, appointmentId)).rejects.toThrowError(RequiredArgumentError) }) }) describe('when requester is given', () => { let appointment: Appointment beforeAll(async () => { requester = await factory.build('user') }) describe('when appointment is not found', () => { beforeEach(() => { appointmentRepository.get.mockResolvedValue(null) }) it('throws error', async () => { await expect(subject.enterVideocall(requester, appointmentId)).rejects.toThrowError(NotFoundError) }) }) describe('when appointment is found', () => { beforeAll(async () => { appointment = await factory.build('appointment') }) beforeEach(() => { appointmentRepository.get.mockResolvedValue(appointment) }) describe('when appointment is cancelled', () => { beforeEach(() => { appointment.state = 'cancelled' }) it('throws error', async () => { await expect(subject.enterVideocall(requester, appointmentId)).rejects.toThrowError(BusinessExceptionError) }) }) describe('when appointment consultation is finished', () => { beforeEach(() => { appointment.state = 'food_plan_pending' }) it('throws error', async () => { await expect(subject.enterVideocall(requester, appointmentId)).rejects.toThrowError(BusinessExceptionError) }) }) describe('when appointment is in_consultation', () => { beforeEach(() => { appointment.state = 'in_consultation' }) it("does not change appointment's state", async () => { await subject.enterVideocall(requester, appointmentId) expect(appointmentRepository.save).not.toBeCalled() }) }) describe('when requester is a patient', () => { beforeAll(async () => { requester = await factory.build('patient') }) it('does not generate video session', async () => { await subject.enterVideocall(requester, appointmentId) expect(vonage.createSession).not.toBeCalled() }) describe("when appointment's state is patient_waiting", () => { beforeEach(() => { appointment.state = 'patient_waiting' }) it("does not change appointment's state", async () => { await subject.enterVideocall(requester, appointmentId) expect(appointmentRepository.save).not.toBeCalled() }) }) describe("when appointment's state is scheduled", () => { beforeEach(() => { appointment.state = 'scheduled' }) it("changes appointment's state to patient_waiting", async () => { await subject.enterVideocall(requester, appointmentId) expect(appointmentRepository.save.mock.calls[0][0]).toEqual( expect.objectContaining({ id: appointment.id, state: 'patient_waiting' }), ) }) }) describe("when appointment's state is user_waiting", () => { beforeEach(() => { appointment.state = 'user_waiting' }) it("changes appointment's state to in_consultation", async () => { await subject.enterVideocall(requester, appointmentId) expect(appointmentRepository.save.mock.calls[0][0]).toEqual( expect.objectContaining({ id: appointment.id, state: 'in_consultation' }), ) }) }) }) describe('when requester is a user', () => { let videoSession: string beforeAll(async () => { requester = await factory.build('user') videoSession = datatype.uuid() }) beforeEach(() => { appointment.state = 'scheduled' vonage.createSession.mockResolvedValue(videoSession) }) it('generates video session', async () => { await subject.enterVideocall(requester, appointmentId) expect(appointmentRepository.save.mock.calls[0][0]).toEqual( expect.objectContaining({ id: appointment.id, video_session: videoSession }), ) }) describe("when appointment's state is user_waiting", () => { beforeEach(() => { appointment.state = 'user_waiting' }) it("does not change appointment's state", async () => { await subject.enterVideocall(requester, appointmentId) expect(appointmentRepository.save).not.toBeCalled() }) }) describe("when appointment's state is scheduled", () => { beforeEach(() => { appointment.state = 'scheduled' }) it("changes appointment's state to user_waiting", async () => { await subject.enterVideocall(requester, appointmentId) expect(appointmentRepository.save.mock.calls[0][0]).toEqual( expect.objectContaining({ id: appointment.id, state: 'user_waiting' }), ) }) }) describe("when appointment's state is patient_waiting", () => { beforeEach(() => { appointment.state = 'patient_waiting' }) it("changes appointment's state to in_consultation", async () => { await subject.enterVideocall(requester, appointmentId) expect(appointmentRepository.save.mock.calls[0][0]).toEqual( expect.objectContaining({ id: appointment.id, state: 'in_consultation' }), ) }) }) }) }) }) }) })