import { Test, TestingModule } from '@nestjs/testing' import { datatype, date } from 'faker' import { sample } from 'lodash' import { factory } from '../../../../test/factories' import { MockedModule } from '../../../../test/types' import { NotFoundError } from '../../../errors' import { Patient, PaymentProviders, Plan, Subscription, SubscriptionInstallment, SubscriptionTransaction, } from '../../../models' import { PatientRepository, PlanRepository, SubscriptionInstallmentRepository, SubscriptionRepository, SubscriptionTransactionRepository, } from '../../../repositories' import { OmieService, Person, SmartfitApi } from '../../../services' import { OmieConfigService } from '../../../services/omie/service' import { Omie } from '../../../typings/global' import { Contract } from './contract' describe('Contract', () => { let subject: Contract let patientRepository: MockedModule let planRepository: MockedModule let subscriptionRepository: MockedModule let subscriptionTransactionRepository: MockedModule let subscriptionInstallmentRepository: MockedModule let omieService: MockedModule let omieConfigService: MockedModule let smartfitApi: MockedModule beforeAll(async () => { patientRepository = { get: jest.fn(), save: jest.fn(), } planRepository = { get: jest.fn(), } subscriptionRepository = { findActiveByTrialExpiresAtAndFirstInstallmentNotInvoiced: jest.fn(), save: jest.fn(), get: jest.fn(), } subscriptionTransactionRepository = { findLastBySubscription: jest.fn(), } subscriptionInstallmentRepository = { save: jest.fn(), getTotalBySubscription: jest.fn(), scheduleServiceOrderInvoicing: jest.fn(), } omieService = { generateServiceOrder: jest.fn(), invoiceServiceOrder: jest.fn(), cancelContract: jest.fn(), cancelInvoice: jest.fn(), } omieConfigService = { getConfig: jest.fn(), } smartfitApi = { getPersonByLogin: jest.fn(), } const app: TestingModule = await Test.createTestingModule({ controllers: [], providers: [ { provide: PatientRepository, useValue: patientRepository, }, { provide: PlanRepository, useValue: planRepository, }, { provide: SubscriptionRepository, useValue: subscriptionRepository, }, { provide: SubscriptionTransactionRepository, useValue: subscriptionTransactionRepository, }, { provide: SubscriptionInstallmentRepository, useValue: subscriptionInstallmentRepository, }, { provide: OmieService, useValue: omieService, }, { provide: OmieConfigService, useValue: omieConfigService, }, { provide: SmartfitApi, useValue: smartfitApi, }, Contract, ], }).compile() subject = app.get(Contract) }) afterEach(() => { jest.resetAllMocks() }) afterAll(() => { jest.resetAllMocks() }) describe('#getPlan', () => { let plan: Plan let id: string let result: Plan beforeAll(() => { id = datatype.uuid() }) beforeEach(() => { planRepository.get.mockResolvedValue(plan) }) afterAll(() => { subject['plans'] = {} }) describe('when id does not found', () => { beforeAll(() => { plan = null }) it('throws error', async () => { await expect(subject['getPlan'](id)).rejects.toThrowError(`Plano ${id} não encontrado`) }) }) describe('when get a plan', () => { beforeAll(async () => { plan = await factory.build('plan:smartfit') id = plan.id }) beforeEach(async () => { result = await subject['getPlan'](id) }) describe('when called the first time', () => { it('calls get plan', () => { expect(planRepository.get).toBeCalled() }) it('returns plan', () => { expect(result).toEqual(plan) }) }) describe('when called the second time', () => { it('does not call get plan', () => { expect(planRepository.get).not.toBeCalled() }) it('returns plan', () => { expect(result).toEqual(plan) }) }) }) }) describe('#checkActivatedSubscriptions', () => { let dateParam: Date let subscriptions: Subscription[] let patient: Patient let plan: Plan let person: Person beforeAll(async () => { dateParam = new Date() }) beforeEach(() => { subscriptionRepository.findActiveByTrialExpiresAtAndFirstInstallmentNotInvoiced.mockResolvedValue(subscriptions) }) describe('when does not found active subscriptions', () => { it('does not schedule the invoicing', async () => { await subject.checkActivatedSubscriptions(dateParam) expect(subscriptionInstallmentRepository.scheduleServiceOrderInvoicing).not.toBeCalled() }) }) describe('when found active subscriptions', () => { const serviceOrder = { code: datatype.number({ min: 1 }), customerCode: datatype.number({ min: 1 }) } let installments: SubscriptionInstallment[] beforeAll(async () => { subscriptions = await factory.buildMany('subscription', datatype.number({ min: 2, max: 10 })) patient = await factory.build('patient:subscriber') plan = await factory.build('plan:n2b') person = await factory.build('smartfit:person') installments = [ SubscriptionInstallment.toInstance({ id: datatype.uuid(), amount: datatype.number({ min: 1, max: 100 }), paid_at: date.recent(1), omie_os_code: null, omie_is_invoiced: null, installment: 1, }), ] subscriptions.forEach(subscription => { subscription.planId = plan.id subscription.paymentProvider = PaymentProviders.N2B subscription.trialExpiresAt = date.recent(3) subscription.installments = installments }) }) beforeEach(() => { patient.subscription.paymentProvider = subscriptions[0].paymentProvider patientRepository.get.mockResolvedValue(patient) patientRepository.save.mockResolvedValue({}) planRepository.get.mockResolvedValue(plan) smartfitApi.getPersonByLogin.mockResolvedValue(person) omieService.generateServiceOrder.mockResolvedValue(serviceOrder) omieService.invoiceServiceOrder.mockResolvedValue({}) }) describe('when subscription is not from N2B Payment', () => { beforeAll(async () => { subscriptions.forEach(subscription => (subscription.paymentProvider = PaymentProviders.SMARTFIT)) }) afterAll(async () => { subscriptions.forEach(subscription => (subscription.paymentProvider = PaymentProviders.N2B)) }) it('does not schedule the invoicing', async () => { await subject.checkActivatedSubscriptions(dateParam) expect(subscriptionInstallmentRepository.scheduleServiceOrderInvoicing).not.toBeCalled() }) }) describe('when subscription already emitted a service order', () => { beforeAll(async () => { subscriptions = subscriptions.slice(0, 1) installments[0].omie_os_code = datatype.uuid() }) afterAll(async () => { installments[0].omie_os_code = null }) describe('and service order is already invoiced', () => { beforeAll(() => { installments[0].omie_is_invoiced = true }) it('does not schedule the invoicing', async () => { await subject.checkActivatedSubscriptions(dateParam) expect(subscriptionInstallmentRepository.scheduleServiceOrderInvoicing).not.toBeCalled() }) }) describe('and service order is not invoiced yet', () => { beforeAll(() => { installments[0].omie_is_invoiced = false }) it('schedules the invoicing', async () => { await subject.checkActivatedSubscriptions(dateParam) expect(subscriptionInstallmentRepository.scheduleServiceOrderInvoicing).toBeCalled() }) }) }) describe('when subscription is trial', () => { beforeAll(async () => { subscriptions = [ await factory.build('subscription', { paymentProvider: PaymentProviders.N2B, trialExpiresAt: date.future(), }), ] }) it('does not schedule the invoicing', async () => { await subject.checkActivatedSubscriptions(dateParam) expect(subscriptionInstallmentRepository.scheduleServiceOrderInvoicing).not.toBeCalled() }) }) describe('when subscription is cancelled', () => { beforeAll(async () => { subscriptions = [ await factory.build('subscription', { paymentProvider: PaymentProviders.N2B, trialExpiresAt: date.recent(), cancellationAt: date.recent(), }), ] }) it('does not schedule the invoicing', async () => { await subject.checkActivatedSubscriptions(dateParam) expect(subscriptionInstallmentRepository.scheduleServiceOrderInvoicing).not.toBeCalled() }) }) }) }) describe('#emitServiceOrder', () => { let subscription: Subscription let patient: Patient let plan: Plan let person: Person let installments: SubscriptionInstallment[] let subscriptionTransaction: SubscriptionTransaction beforeAll(async () => { subscription = await factory.build('subscription:n2b', { trialExpiresAt: date.recent(1), cancelledAt: null }) patient = await factory.build('patient:subscriber', { subscription, id: subscription.patientId }) plan = await factory.build('plan:n2b') person = await factory.build('smartfit:person') subscriptionTransaction = await factory.build('subscription_transaction:success', { card_type: 'credit' }) subject['plans'] = {} installments = [ SubscriptionInstallment.toInstance({ id: datatype.uuid(), amount: datatype.number({ min: 1, max: 100 }), paid_at: date.recent(1), installment: 1, omie_is_invoiced: false, omie_os_code: null, }), ] }) beforeEach(() => { patientRepository.get.mockResolvedValue(patient) smartfitApi.getPersonByLogin.mockResolvedValue(person) omieService.generateServiceOrder.mockResolvedValue({ customerCode: datatype.number({ min: 1 }), code: datatype.number({ min: 1 }), }) omieService.invoiceServiceOrder.mockResolvedValue({}) subscriptionTransactionRepository.findLastBySubscription.mockResolvedValue(subscriptionTransaction) subscriptionInstallmentRepository.getTotalBySubscription.mockResolvedValue(12) }) describe('when plan is not found', () => { it('throws an error', async () => { await expect(subject['emitServiceOrder'](subscription, installments[0])).rejects.toThrow(NotFoundError) }) }) describe('when plan is found', () => { beforeEach(() => { planRepository.get.mockResolvedValue(plan) }) it('generates a service order', async () => { await subject['emitServiceOrder'](subscription, installments[0]) expect(omieService.generateServiceOrder).toBeCalled() }) describe('when installment already has omie code', () => { beforeAll(() => { installments[0].omie_os_code = datatype.number({ min: 1 }).toString() }) afterAll(() => { installments[0].omie_os_code = null }) it('does not generate service order', async () => { await subject['emitServiceOrder'](subscription, installments[0]) expect(omieService.generateServiceOrder).not.toBeCalled() }) describe('and service order is already invoiced', () => { beforeAll(() => { installments[0].omie_is_invoiced = true }) it('does not invoice the service order', async () => { await subject['emitServiceOrder'](subscription, installments[0]) expect(omieService.invoiceServiceOrder).not.toBeCalled() }) }) describe('and service order is not invoiced yet', () => { beforeAll(() => { installments[0].omie_is_invoiced = false }) it('invoices the service order', async () => { await subject['emitServiceOrder'](subscription, installments[0]) expect(omieService.invoiceServiceOrder).toBeCalledWith(+installments[0].omie_os_code) }) }) }) describe('when invoice service order throws an error', () => { beforeEach(() => { omieService.invoiceServiceOrder.mockRejectedValueOnce(new Error('Error')) }) it('saves service order code in subscription', async () => { await subject['emitServiceOrder'](subscription, installments[0]) const serviceOrder = await omieService.generateServiceOrder.mock.results[0].value expect(subscriptionInstallmentRepository.save).toHaveBeenCalledWith( expect.objectContaining({ omie_os_code: serviceOrder.code.toString(), }), expect.arrayContaining(['omie_os_code']), ) }) }) describe('when invoice service order is called successfully', () => { beforeEach(() => { omieService.invoiceServiceOrder.mockResolvedValueOnce({}) }) it('saves service order code and invoiced flag in subscription', async () => { await subject['emitServiceOrder'](subscription, installments[0]) const serviceOrder = await omieService.generateServiceOrder.mock.results[0].value expect(subscriptionInstallmentRepository.save).toHaveBeenCalledWith( expect.objectContaining({ omie_os_code: serviceOrder.code.toString(), omie_is_invoiced: true, }), ['omie_os_code', 'omie_is_invoiced'], ) }) }) describe(`when patient haven't omie_code`, () => { it('saves patient omie_code', async () => { await subject['emitServiceOrder'](subscription, installments[0]) const serviceOrder = await omieService.generateServiceOrder.mock.results[0].value expect(patientRepository.save).toBeCalledWith( expect.objectContaining({ omie_code: serviceOrder.customerCode.toString() }), ['omie_code'], ) }) }) }) }) describe('#cancel', () => { let subscription: Subscription let omieConfig: Partial beforeAll(() => { omieConfig = { enable_cancel: true, } }) beforeEach(() => { omieConfigService.getConfig.mockResolvedValue(omieConfig) }) describe('when subscription is not cancelled', () => { beforeAll(async () => { subscription = await factory.build('subscription', { paymentProvider: PaymentProviders.N2B }) }) it('does not cancel the contract', async () => { await subject.cancel(subscription) expect(omieService.cancelContract).not.toBeCalled() }) }) describe('when subscription is not N2B payment provider', () => { beforeAll(async () => { subscription = await factory.build('subscription', { paymentProvider: sample([PaymentProviders.SMARTFIT, PaymentProviders.APPLE, PaymentProviders.GOOGLE]), }) }) it('does not cancel the contract', async () => { await subject.cancel(subscription) expect(omieService.cancelContract).not.toBeCalled() }) }) describe('when subscription is cancelled', () => { beforeAll(async () => { subscription = await factory.build('subscription', { cancellationAt: date.recent(), omieContractCode: datatype.number({ min: 1 }), omieOSCode: datatype.number({ min: 1 }), paymentProvider: PaymentProviders.N2B, }) }) it('cancels the invoice', async () => { await subject.cancel(subscription) expect(omieService.cancelInvoice).toBeCalledWith(subscription.omieOSCode) }) it('cancels the contract', async () => { await subject.cancel(subscription) expect(omieService.cancelContract).toBeCalledWith(subscription.omieContractCode) }) describe('when cancel Omie contract is disabled', () => { beforeAll(() => { omieConfig.enable_cancel = false }) it('does not cancel the contract', async () => { await subject.cancel(subscription) expect(omieService.cancelContract).not.toBeCalled() }) }) }) }) describe('#emitServiceOrderForInstallment', () => { let subscription: Subscription let installment: SubscriptionInstallment let subscriptionTransaction: SubscriptionTransaction let plan: Plan let person: Person let patient: Patient beforeAll(async () => { subscription = await factory.build('subscription:n2b') plan = await factory.build('plan:n2b') person = await factory.build('smartfit:person') patient = await factory.build('patient:subscriber', { subscription, id: subscription.patientId }) subscriptionTransaction = await factory.build('subscription_transaction:success', { card_type: 'credit' }) installment = { id: datatype.uuid(), amount: datatype.number({ min: 1, max: 100 }), paid_at: date.recent(1), installment: 1, subscription_id: subscription.id, created_at: date.recent(1), updated_at: date.recent(1), toPlain: () => { '' }, } }) beforeEach(() => { subscriptionRepository.get.mockResolvedValue(subscription) subscriptionInstallmentRepository.getTotalBySubscription.mockResolvedValue(12) subscriptionTransactionRepository.findLastBySubscription.mockResolvedValue(subscriptionTransaction) planRepository.get.mockResolvedValue(plan) smartfitApi.getPersonByLogin.mockResolvedValue(person) patientRepository.get.mockResolvedValue(patient) omieService.generateServiceOrder.mockResolvedValue({ customerCode: datatype.number({ min: 1 }), code: datatype.number({ min: 1 }), }) }) describe('when installment is not paid', () => { beforeEach(() => { installment.paid_at = null }) it("don't emit service order ", async () => { await subject.emitServiceOrderForInstallment(installment) expect(omieService.generateServiceOrder).not.toBeCalled() }) }) describe('when installment is not the first', () => { beforeEach(() => { installment.paid_at = date.recent(1) installment.installment = datatype.number({ min: 2 }) }) it('emits service order ', async () => { await subject.emitServiceOrderForInstallment(installment) expect(omieService.generateServiceOrder).toBeCalled() }) }) describe('when installment is first', () => { beforeEach(() => { installment.installment = 1 }) describe('and the subscription is trial', () => { beforeEach(() => { subscription.trialExpiresAt = date.soon(1) }) it('does not emit service order ', async () => { await subject.emitServiceOrderForInstallment(installment) expect(omieService.generateServiceOrder).not.toBeCalled() }) }) describe('and the subscription is not trial', () => { beforeEach(() => { subscription.trialExpiresAt = date.recent(1) }) it('emits service order ', async () => { await subject.emitServiceOrderForInstallment(installment) expect(omieService.generateServiceOrder).toBeCalled() }) }) }) }) })