import { TestingModule, Test } from '@nestjs/testing' import { datatype } from 'faker' import MockDate from 'mockdate' import { factory } from '../../../../../test/factories' import { MockedModule } from '../../../../../test/types' import { PlanRepository } from '../../../../repositories' import { AppleInAppSubscriptionService, AppleInAppSubscription, SubscriptionService } from '../../../../services' import { CancellationsReasons, Patient, PaymentProviders, Plan, Subscription } from './../../../../models' import { AppleCheckSubscription } from './apple.check-subscription' describe('AppleCheckSubscription', () => { let subject: AppleCheckSubscription let subscriptionService: MockedModule let planRepository: MockedModule let appleInAppSubscription: MockedModule let now: Date beforeAll(async () => { subscriptionService = { addSubscription: jest.fn(), cancelSubscription: jest.fn(), getToken: jest.fn(), } planRepository = { findByAddonCode: jest.fn(), getWithFeatures: jest.fn(), } appleInAppSubscription = { getInfo: jest.fn(), } const app: TestingModule = await Test.createTestingModule({ controllers: [], providers: [ { provide: SubscriptionService, useValue: subscriptionService, }, { provide: PlanRepository, useValue: planRepository, }, { provide: AppleInAppSubscriptionService, useValue: appleInAppSubscription, }, AppleCheckSubscription, ], }).compile() subject = app.get(AppleCheckSubscription) now = new Date() MockDate.set(now) }) afterEach(() => { jest.resetAllMocks() }) afterAll(() => { jest.restoreAllMocks() }) describe('#check()', () => { let patient: Patient let planByCode: Plan let appleInfo: AppleInAppSubscription let newSubscription: Subscription beforeAll(async () => { appleInfo = await factory.build('apple:subscription_info') planByCode = await factory.build('plan', { payment_providers: [ await factory.build('plan_payment_provider', { provider: PaymentProviders.APPLE, code: appleInfo.externalPlan, }), ], }) newSubscription = await factory.build('subscription:apple') }) beforeEach(() => { newSubscription.planId = planByCode.id subscriptionService.addSubscription.mockResolvedValue(newSubscription) subscriptionService.getToken.mockResolvedValue(patient?.subscription?.token) planRepository.findByAddonCode.mockResolvedValue(planByCode) }) describe('when patient is not subscriber', () => { beforeEach(async () => { patient = await factory.build('patient') }) it('does not cancel subscription', async () => { await subject.check(patient) expect(subscriptionService.cancelSubscription).not.toBeCalled() }) describe('when has apple subscription', () => { beforeEach(() => { appleInAppSubscription.getInfo.mockResolvedValue(appleInfo) }) it('creates subscription', async () => { await subject.check(patient) expect(subscriptionService.addSubscription).toBeCalledWith( expect.objectContaining({ patient, plan: planByCode, paymentProvider: { provider: PaymentProviders.APPLE }, options: { subscribedAt: appleInfo.purchasedAt, originalTransaction: appleInfo.originalTransaction, token: appleInfo.receipt, }, }), ) }) it('returns true', async () => { await expect(subject.check(patient)).resolves.toBeTruthy() }) }) describe('when does not have apple subscription', () => { beforeAll(() => { appleInAppSubscription.getInfo.mockResolvedValue(null) }) it('does not create subscription', async () => { await subject.check(patient) expect(subscriptionService.addSubscription).not.toBeCalled() }) it('returns false', async () => { await expect(subject.check(patient)).resolves.toBeFalsy() }) }) }) describe('when patient is subscriber', () => { let plan: Plan beforeAll(async () => { plan = await factory.build('plan', { payment_providers: [ await factory.build('plan_payment_provider', { provider: PaymentProviders.APPLE, code: appleInfo.externalPlan, }), ], }) }) beforeEach(async () => { patient = await factory.build('patient:subscriber') planRepository.getWithFeatures.mockResolvedValue(plan) }) describe('when has apple subscription', () => { beforeEach(() => { appleInAppSubscription.getInfo.mockResolvedValue(appleInfo) }) describe('when the plan is same from apple', () => { it('does not cancel subscription', async () => { await subject.check(patient) expect(subscriptionService.cancelSubscription).not.toBeCalled() }) it('returns true', async () => { await expect(subject.check(patient)).resolves.toBeTruthy() }) }) describe('when the plan is not same from apple', () => { beforeAll(() => { plan.payment_providers[0].code = datatype.uuid() }) beforeEach(() => { subscriptionService.cancelSubscription.mockImplementation(({ patient }: { patient: Patient }) => { patient.subscription.cancellationRequestAt = new Date() patient.subscription.cancellationAt = new Date() }) }) it('cancels subscription', async () => { const oldExpiresAt = patient.subscription.expiresAt await subject.check(patient) expect(subscriptionService.cancelSubscription).toBeCalledWith({ patient, cancellationReason: { id: CancellationsReasons.CHANGE_PLAN_BY_PROVIDER }, cancellationBy: { id: patient.id }, expiresAt: oldExpiresAt || appleInfo?.expiration, }) }) it('creates new subscription with correct plan', async () => { await subject.check(patient) expect(subscriptionService.addSubscription).toBeCalledWith( expect.objectContaining({ patient, plan: planByCode, paymentProvider: { provider: PaymentProviders.APPLE }, options: { subscribedAt: appleInfo.purchasedAt, originalTransaction: appleInfo.originalTransaction, token: appleInfo.receipt, }, }), ) }) it('returns true', async () => { await expect(subject.check(patient)).resolves.toBeTruthy() }) }) }) describe('when does not have apple subscription', () => { beforeEach(() => { appleInAppSubscription.getInfo.mockResolvedValue(null) subscriptionService.cancelSubscription.mockImplementation(({ patient }: { patient: Patient }) => { patient.subscription.cancellationRequestAt = new Date() patient.subscription.cancellationAt = new Date() }) }) it('cancels subscription', async () => { await subject.check(patient) expect(subscriptionService.cancelSubscription).toBeCalledWith({ patient, cancellationReason: { id: CancellationsReasons.BY_PROVIDER }, cancellationBy: { id: patient.id }, expiresAt: patient.subscription.expiresAt || now, }) }) it('does not create new subscription', async () => { await subject.check(patient) expect(subscriptionService.addSubscription).not.toBeCalled() }) it('returns false', async () => { await expect(subject.check(patient)).resolves.toBeFalsy() }) }) }) }) })