import { TestingModule, Test } from '@nestjs/testing' import { datatype } from 'faker' import { factory } from '../../../../test/factories' import { MockedModule } from '../../../../test/types' import { Availability, AvailabilityTime } from '../../../models' import { AvailabilityRepository, AvailabilityTimeRepository } from '../../../repositories' import { Availabilities } from './availabilities' describe('Availabilities', () => { let subject: Availabilities let availabilityTimeRepository: MockedModule let availabilityRepository: MockedModule beforeAll(async () => { availabilityTimeRepository = { getByAvailabilityId: jest.fn(), save: jest.fn(), create: jest.fn(), delete: jest.fn(), } availabilityRepository = { createWithNestedAvailabilityTimes: jest.fn(), getByUserAndDate: jest.fn(), delete: jest.fn(), } const app: TestingModule = await Test.createTestingModule({ controllers: [], providers: [ { provide: AvailabilityTimeRepository, useValue: availabilityTimeRepository, }, { provide: AvailabilityRepository, useValue: availabilityRepository, }, Availabilities, ], }).compile() subject = app.get(Availabilities) }) afterEach(() => { jest.clearAllMocks() }) afterAll(() => { jest.restoreAllMocks() }) describe('#generate', () => { let availability: Availability let existingAvailability: Availability let availabilityTimes: Array let existingAvailabilityTimes: Array beforeEach(() => { availabilityRepository.getByUserAndDate.mockResolvedValue(existingAvailability) availabilityTimeRepository.getByAvailabilityId.mockResolvedValue(existingAvailabilityTimes) }) describe('when not already exists a availability with the given date', () => { beforeAll(() => { existingAvailability = null existingAvailabilityTimes = [] }) beforeEach(async () => { availability = await factory.build('availability') availabilityTimes = await factory.buildMany('availability_time', datatype.number({ min: 1, max: 10 })) }) it('creates the availability and his availability times', async () => { await subject.generate(availability, availabilityTimes) expect(availabilityRepository.createWithNestedAvailabilityTimes).toHaveBeenCalledWith( Availability.toInstance(availability), availabilityTimes.map(availabilityTime => { availabilityTime.setOffsetInTime(availability.date) return availabilityTime }), ) }) }) describe('when exists a availability with the given date', () => { beforeAll(async () => { existingAvailability = await factory.build('availability') }) beforeEach(async () => { availability = await factory.build('availability') availabilityTimes = await factory.buildMany('availability_time', datatype.number({ min: 1, max: 10 })) }) describe('and has availability times that already exists', () => { beforeAll(async () => { existingAvailabilityTimes = await factory.buildMany('availability_time', datatype.number({ min: 1, max: 10 }), { availability_id: existingAvailability.id, }) }) describe('and exists availability times with the same time and timezone of new ones', () => { beforeEach(() => { existingAvailabilityTimes[0].time = availabilityTimes[0].time existingAvailabilityTimes[0].timezone = availabilityTimes[0].timezone existingAvailabilityTimes[0].setOffsetInTime(availability.date) }) describe.each` status | willDelete ${'available'} | ${false} ${'blocked'} | ${false} ${'scheduled'} | ${false} `('and is "$status"', ({ status, willDelete }) => { beforeEach(() => { existingAvailabilityTimes[0].status = status }) if (status === 'available') { it(`doesn't save the availability time`, async () => { await subject.generate(availability, availabilityTimes) expect(availabilityTimeRepository.save).not.toHaveBeenCalledWith( expect.objectContaining({ id: existingAvailabilityTimes[0].id }), ) }) } it(`${!willDelete ? `doesn't ` : ''}delete the availability time`, async () => { const id = existingAvailabilityTimes[0].id await subject.generate(availability, availabilityTimes) const expected = expect(availabilityTimeRepository.delete) if (willDelete) return expected.toBeCalledWith(id) return expected.not.toBeCalledWith(id) }) }) }) describe(`and exists availability times that doens't is the same in the new ones`, () => { describe.each` status | willDelete ${'available'} | ${true} ${'blocked'} | ${false} ${'scheduled'} | ${false} `('and is "$status"', ({ status, willDelete }) => { beforeEach(() => { existingAvailabilityTimes[0].time = '00:01' existingAvailabilityTimes[0].timezone = availabilityTimes[0].timezone existingAvailabilityTimes[0].status = status existingAvailabilityTimes[0].setOffsetInTime(availability.date) }) it(`${!willDelete ? `doesn't ` : ''}delete the availability time`, async () => { const id = existingAvailabilityTimes[0].id await subject.generate(availability, availabilityTimes) const expected = expect(availabilityTimeRepository.delete) if (willDelete) return expected.toBeCalledWith(id) return expected.not.toBeCalledWith(id) }) }) describe('and time is the same', () => { beforeEach(() => { existingAvailabilityTimes[0].time = availabilityTimes[0].time }) describe('and status is different', () => { beforeEach(() => { availabilityTimes[0].status = 'available' availabilityTimes[0].timezone = 'America/Sao_Paulo' existingAvailabilityTimes[0].status = 'blocked' existingAvailabilityTimes[0].timezone = 'America/Sao_Paulo' }) it('update existing availability time', async () => { await subject.generate(availability, availabilityTimes) availabilityTimes[0].id = existingAvailabilityTimes[0].id availabilityTimes[0].setOffsetInTime(existingAvailability.date) expect(availabilityTimeRepository.save).toHaveBeenCalledWith(availabilityTimes[0], [ { field: 'status', type: 'availability_time_status_enum' }, { field: 'time', type: 'timetz' }, 'timezone', ]) }) }) describe('and timezone is different', () => { beforeEach(() => { availabilityTimes[0].status = 'available' availabilityTimes[0].timezone = 'America/Sao_Paulo' existingAvailabilityTimes[0].status = 'available' existingAvailabilityTimes[0].timezone = 'America/Santiago' }) it('create a new availability time', async () => { await subject.generate(availability, availabilityTimes) availabilityTimes[0].availability_id = existingAvailability.id availabilityTimes[0].setOffsetInTime(existingAvailability.date) expect(availabilityTimeRepository.create).toHaveBeenCalledWith(availabilityTimes[0]) }) }) }) }) describe(`when doens't has availability times to generate`, () => { beforeEach(() => { availabilityTimes = [] }) describe(`when all availability times was excluded`, () => { beforeAll(async () => { existingAvailabilityTimes = await factory.buildMany( 'availability_time', datatype.number({ min: 1, max: 10 }), { availability_id: existingAvailability.id, status: 'available', }, ) }) it('deletes the availability', async () => { await subject.generate(availability, availabilityTimes) expect(availabilityRepository.delete).toHaveBeenCalledWith(existingAvailability.id) }) it('returns null', async () => { expect(await subject.generate(availability, availabilityTimes)).toBeNull() }) }) }) }) describe('when has new availability times', () => { it('creates new availability times', async () => { await subject.generate(availability, availabilityTimes) availabilityTimes[0].setOffsetInTime(availability.date) availabilityTimes[0].availability_id = existingAvailability.id expect(availabilityTimeRepository.create).toHaveBeenCalledWith(availabilityTimes[0]) }) }) describe(`when hasn't new availability times`, () => { beforeEach(() => { availabilityTimes = [] }) it('deletes the availability', async () => { await subject.generate(availability, availabilityTimes) expect(availabilityRepository.delete).toHaveBeenCalledWith(existingAvailability.id) }) it('returns null', async () => { expect(await subject.generate(availability, availabilityTimes)).toBeNull() }) }) }) }) })