// @ts-nocheck import { expect } from 'chai'; import * as sinon from 'sinon'; import { TestConfiguration } from '../../src/configuration'; import { TransitionInfo } from '../../src/models/transition-info'; import { Transition } from '../../src/transition'; import { standardUpgrade, subscriptionTransition } from '../fixtures/transition'; const sandbox = sinon.createSandbox(); describe('Transition SDK', () => { let instance; let getStub; let postStub; let deleteStub; let config; const originSystemId = 'n-membership-sdk-tests'; const transitionApiKey = 'my-api-key'; beforeEach(() => { config = new TestConfiguration({ originSystemId, transitionApiKey }); instance = new Transition(config); getStub = sandbox.stub(instance, 'requestGet').resolves({}); postStub = sandbox.stub(instance, 'requestPost').resolves({}); deleteStub = sandbox.stub(instance, 'requestDelete').resolves({}); }); it('requires host and key configuration properties', () => { try { new Transition({}); expect.fail('It should throw'); } catch (error) { } }); describe('TYPE', () => { it('has the correct transition types exposed', () => { expect(Transition.TYPE).to.have.property('Immediate', 'immediate'); expect(Transition.TYPE).to.have.property('EndOfTerm', 'endOfTerm'); }); }); describe('getUserTransitionInfo', () => { const userId = 'abc'; const offerId = 'abc'; const countryCode = 'GBR'; const type = 'immediate'; beforeEach(() => { getStub.resolves(standardUpgrade); }); context('should pass correct parameters to requestGet', async () => { let params; beforeEach(async () => { await instance.getUserTransitionInfo(userId, offerId, countryCode, type); [params] = instance.requestGet.getCall(0).args; }); it('should pass `url` parameter to requestGet', async () => { expect(params).to.have.property('url', `${instance.membershipApi}/subs/transitions/subscriptions/transition-info?userId=abc&billingInfo=true&selfServe=true&offerId=abc&countryCode=GBR&type=immediate&checkForStepUpOptions=false`); }); it('should pass `key` parameter to requestGet', async () => { expect(params).to.have.property('key', transitionApiKey); }); }); context('Step Up', async () => { it('should pass `checkForStepUpOptions` parameter to requestGet and it should be `false` by default', async () => { await instance.getUserTransitionInfo(userId, offerId, countryCode, type); let [params] = instance.requestGet.getCall(0).args; expect(params.url).to.contain('checkForStepUpOptions=false'); }); it('should pass `checkForStepUpOptions` parameter to requestGet when required', async () => { await instance.getUserTransitionInfo(userId, offerId, countryCode, type, true); let [params] = instance.requestGet.getCall(0).args; expect(params.url).to.contain('checkForStepUpOptions=true'); }); }); it('should return a TransitionInfo model', async () => { const result = await instance.getUserTransitionInfo(userId, offerId, countryCode, type); expect(result).to.be.an.instanceOf(TransitionInfo); }); context('when an error is thrown', () => { const errorResponse = new Error('Test failure'); beforeEach(() => { instance.requestGet.rejects(errorResponse); }); it('it throws the same error', async () => { try { await instance.getUserTransitionInfo(userId, offerId, countryCode, type); expect.fail('Should have thrown the error!'); } catch (error) { expect(error).to.deep.equal(errorResponse); } }); }); }); describe('applyTransition', () => { const subscriptionNumber = 'abc'; const offerId = 'abc'; let term = 'abc'; const countryCode = 'GBR'; const type = 'immediate'; let isSingleTerm = false; const fulfilmentOption = 'HD'; beforeEach(() => { postStub.resolves(subscriptionTransition); }); context('should pass correct parameters to requestPost', async () => { let params; beforeEach(async () => { await instance.applyTransition(subscriptionNumber, offerId, term, type, countryCode); [params] = instance.requestPost.getCall(0).args; }); it('should pass api parameter to requestPost', async () => { let additionalHeaders = { 'X-Origin-System-Id': 'n-membership-sdk-tests', 'X-Origin-User': 'CS' }; expect(params).to.have.property('additionalHeaders'); expect(params.additionalHeaders).to.eql(additionalHeaders); }); it('should pass url parameter to requestPost', async () => { expect(params).to.have.property('url', `${instance.membershipApi}/subs/transitions/subscriptions/transition/v2/${subscriptionNumber}?cancelFutureAmendments=false`); }); it('should pass key parameter to requestPost', async () => { expect(params).to.have.property('key', transitionApiKey); }); it('should pass body parameter to requestPost', () => { expect(params.body).to.deep.equal({ offerId: 'abc', subscriptionTerm: { autoRenewTerm: !isSingleTerm, iso8601Duration: term, termType: 'EVERGREEN', }, type: 'immediate', countryCode: 'GBR', email: true, }); }); }); it('should return data', async () => { const result = await instance.applyTransition(subscriptionNumber, offerId, term, type, countryCode, isSingleTerm, fulfilmentOption); expect(result).to.deep.equal(subscriptionTransition); }); it('should call requestPost with EVERGREEN as termType if term is not P1Y and isSingleTerm is false', async () => { let params; await instance.applyTransition(subscriptionNumber, offerId, term, type, countryCode, isSingleTerm); [params] = instance.requestPost.getCall(0).args; expect(params.body).to.deep.equal({ offerId: 'abc', subscriptionTerm: { autoRenewTerm: !isSingleTerm, iso8601Duration: term, termType: 'EVERGREEN', }, type: 'immediate', countryCode: 'GBR', email: true, }); }); it('should call requestPost with correct term and as autoRenew if no optional parameters are passed to applyTransition', async () => { let params; await instance.applyTransition(subscriptionNumber, offerId, term, type, countryCode); [params] = instance.requestPost.getCall(0).args; //term is not P1Y and subscription is not single term so termType should be EVERGREEN expect(params.body).to.deep.equal({ offerId: 'abc', subscriptionTerm: { autoRenewTerm: true, iso8601Duration: term, termType: 'EVERGREEN', }, type: 'immediate', countryCode: 'GBR', email: true, }); }); it('should call requestPost with TERMED as termType if term is P1Y', async () => { let params; term = 'P1Y'; isSingleTerm = false; await instance.applyTransition(subscriptionNumber, offerId, term, type, countryCode, isSingleTerm); [params] = instance.requestPost.getCall(0).args; expect(params.body).to.deep.equal({ offerId: 'abc', subscriptionTerm: { autoRenewTerm: !isSingleTerm, iso8601Duration: term, termType: 'TERMED', }, type: 'immediate', countryCode: 'GBR', email: true, }); term = 'abc'; }); it('should call requestPost with autoRenewTerm set to false and termType set to TERMED if isSingleTerm', async () => { let params; isSingleTerm = true; await instance.applyTransition(subscriptionNumber, offerId, term, type, countryCode, isSingleTerm, 'HD', false); [params] = instance.requestPost.getCall(0).args; expect(params.body).to.deep.equal({ offerId: 'abc', subscriptionTerm: { autoRenewTerm: false, iso8601Duration: term, termType: 'TERMED', }, type: 'immediate', countryCode: 'GBR', email: true, option: 'HD', hidePaymentMethod: false }); isSingleTerm = false; }); it('should call requestPost with hidePaymentMethod set to true if isSingleTerm and hidePaymentMethod are true', async () => { let params; isSingleTerm = true; await instance.applyTransition(subscriptionNumber, offerId, term, type, countryCode, isSingleTerm, 'HD', true); [params] = instance.requestPost.getCall(0).args; expect(params.body.hidePaymentMethod).to.equal(true); isSingleTerm = false; }); it('should call requestPost with the fulfilmentOption if it is passed to applyTransition (even if not renewing to single term)', async () => { let params; await instance.applyTransition(subscriptionNumber, offerId, term, type, countryCode, false, 'HD'); [params] = instance.requestPost.getCall(0).args; expect(params.body).to.deep.equal({ offerId: 'abc', subscriptionTerm: { autoRenewTerm: true, iso8601Duration: term, termType: 'EVERGREEN', }, type: 'immediate', countryCode: 'GBR', email: true, option: 'HD' }); }); it('should call requestPost with TERMED as termType if term contains Y (is annual)', async () => { let params; term = 'P2Y'; isSingleTerm = false; await instance.applyTransition(subscriptionNumber, offerId, term, type, countryCode, isSingleTerm); [params] = instance.requestPost.getCall(0).args; expect(params.body.subscriptionTerm.termType).to.equal('TERMED'); term = 'abc'; }); it('should call requestPost with TERMED as termType if term is P13M', async () => { let params; term = 'P13M'; isSingleTerm = false; await instance.applyTransition(subscriptionNumber, offerId, term, type, countryCode, isSingleTerm); [params] = instance.requestPost.getCall(0).args; expect(params.body.subscriptionTerm.termType).to.equal('TERMED'); term = 'abc'; }); it('should call requestPost with TERMED as termType if term is P26M', async () => { let params; term = 'P26M'; isSingleTerm = false; await instance.applyTransition(subscriptionNumber, offerId, term, type, countryCode, isSingleTerm); [params] = instance.requestPost.getCall(0).args; expect(params.body.subscriptionTerm.termType).to.equal('TERMED'); term = 'abc'; }); context('when an error is thrown', () => { const errorResponse = new Error('Test failure'); beforeEach(() => { instance.requestPost.rejects(errorResponse); }); it('it throws the same error', async () => { try { await instance.applyTransition(subscriptionNumber, offerId, term, type, countryCode); expect.fail('Should have thrown the error!'); } catch (error) { expect(error).to.deep.equal(errorResponse); } }); }); }); describe('cancelSubscription', () => { const userId = 'abc'; const subscriptionNumber = 'subscription-number'; const cancellationReason = 'too-expensive'; const mockResponse = { status: 200, cancellationEffectiveDate: '2012-02-16T00:00:00.000Z', subscriptionKey: 'a-123456' }; beforeEach(() => { instance.requestPost.resolves(mockResponse); }); it('should pass correct parameters to requestPost', async () => { const url = `${instance.membershipApi}/subs/transitions/subscriptions/cancel/v2/${subscriptionNumber}?cancelFutureAmendments=true`; await instance.cancelSubscription(userId, subscriptionNumber, cancellationReason, false); const postParams = instance.requestPost.getCall(0).args[0]; // Path checks expect(postParams).to.have.property('url'); expect(postParams.url).to.equal(url); // Body checks expect(postParams).to.have.property('additionalHeaders'); expect(postParams).to.have.property('body'); expect(postParams.body).to.have.property('cancellationReason'); expect(postParams.body).to.have.property('cancellationType'); expect(postParams.body).to.have.property('userId'); expect(postParams.body).to.have.property('refundType'); }); it('when user ID is not set it throws a ValidationError', async () => { try { await instance.cancelSubscription(null, subscriptionNumber, cancellationReason, false); expect.fail('Should have thrown the error!'); } catch (error) { expect(error.message).to.equal('User ID is not defined'); } }); it('when subscription number is not set it throws a ValidationError', async () => { try { await instance.cancelSubscription(userId, null, cancellationReason, false); expect.fail('Should have thrown the error!'); } catch (error) { expect(error.message).to.equal('Subscription Number is not defined'); } }); context('when no errors are returned', () => { beforeEach(() => { instance.requestPost.resolves(mockResponse); }); it('returns the result', async () => { const data = await instance.cancelSubscription(userId, subscriptionNumber, cancellationReason, false); expect(data).to.deep.equal(mockResponse); }); }); context('when an empty response with status 200 is returned', () => { it('should throw an error', async () => { instance.requestPost.resolves(null); try { await instance.cancelSubscription(userId, subscriptionNumber, cancellationReason, false); expect.fail('Should have thrown the error!'); } catch (error) { expect(error.message).to.equal('Empty subscription cancellation response.'); expect(error.data).to.deep.equal({ userId: userId, additionalHeaders: { 'Cache-Control': 'no-cache', }, subscriptionNumber, cancellationReason, isCustomerServiceAgent: false, response: null }); } }); }); context('when the response is returned with success: false', () => { it('should throw an error', async () => { instance.requestPost.resolves({ success: false }); try { await instance.cancelSubscription(userId, subscriptionNumber, cancellationReason, false); expect.fail('Should have thrown the error!'); } catch (error) { expect(error.message).to.equal('Response returned with an unsuccessful result.'); expect(error.data).to.deep.equal({ userId: userId, additionalHeaders: { 'Cache-Control': 'no-cache', }, subscriptionNumber, cancellationReason, isCustomerServiceAgent: false, response: { success: false } }); } }); }); context('when an error is thrown', () => { const errorResponse = new Error('Test failure'); beforeEach(() => { instance.requestPost.rejects(errorResponse); }); it('it throws the same error', async () => { try { await instance.cancelSubscription(userId, subscriptionNumber, cancellationReason, false); expect.fail('Should have thrown the error!'); } catch (error) { expect(error).to.deep.equal(errorResponse); } }); }); it('passes cancellationEffectiveDate to requestPost if provided', async () => { const url = `${instance.membershipApi}/subs/transitions/subscriptions/cancel/v2/${subscriptionNumber}?cancelFutureAmendments=true`; await instance.cancelSubscription(userId, subscriptionNumber, cancellationReason, false, '2020-11-25'); const postParams = instance.requestPost.getCall(0).args[0]; expect(postParams.body).to.have.property('cancellationEffectiveDate'); expect(postParams.body.cancellationEffectiveDate).to.equal('2020-11-25'); expect(postParams.url).to.equal(url); }); it('passes cancellationEffectiveDate as `undefined` to requestPost if not provided', async () => { const url = `${instance.membershipApi}/subs/transitions/subscriptions/cancel/v2/${subscriptionNumber}?cancelFutureAmendments=true`; await instance.cancelSubscription(userId, subscriptionNumber, cancellationReason, false); const postParams = instance.requestPost.getCall(0).args[0]; expect(postParams.body).to.have.property('cancellationEffectiveDate'); expect(postParams.body.cancellationEffectiveDate).to.be.undefined; expect(postParams.url).to.equal(url); }); it('should set email property of request body to true if send email is not set', async () => { const url = `${instance.membershipApi}/subs/transitions/subscriptions/cancel/v2/${subscriptionNumber}?cancelFutureAmendments=true`; await instance.cancelSubscription(userId, subscriptionNumber, cancellationReason, false); const postParams = instance.requestPost.getCall(0).args[0]; expect(postParams.body).to.have.property('email'); expect(postParams.body.email).to.be.true; expect(postParams.url).to.equal(url); }); it('should set email property of request body to true if send email is set to true', async () => { const url = `${instance.membershipApi}/subs/transitions/subscriptions/cancel/v2/${subscriptionNumber}?cancelFutureAmendments=true`; await instance.cancelSubscription(userId, subscriptionNumber, cancellationReason, undefined, true); const postParams = instance.requestPost.getCall(0).args[0]; expect(postParams.body).to.have.property('email'); expect(postParams.body.email).to.be.true; expect(postParams.url).to.equal(url); }); it('should set email property of request body to false if send email is set to false', async () => { const url = `${instance.membershipApi}/subs/transitions/subscriptions/cancel/v2/${subscriptionNumber}?cancelFutureAmendments=true`; await instance.cancelSubscription(userId, subscriptionNumber, cancellationReason, false, undefined, false); const postParams = instance.requestPost.getCall(0).args[0]; expect(postParams.body).to.have.property('email'); expect(postParams.body.email).to.be.false; expect(postParams.url).to.equal(url); }); it('defaults cancellationType to `endOfTerm` if not given', async () => { await instance.cancelSubscription(userId, subscriptionNumber, cancellationReason); const postParams = instance.requestPost.getCall(0).args[0]; expect(postParams.body.cancellationType).to.equal('endOfTerm'); }); it('passes cancellationType to requestPost if provided', async () => { const cancellationType = 'someType'; await instance.cancelSubscription(userId, subscriptionNumber, cancellationReason, false, null, false, cancellationType); const postParams = instance.requestPost.getCall(0).args[0]; expect(postParams.body.cancellationType).to.equal('someType'); }); it('defaults refundType to `ELECTRONIC` if not given', async () => { await instance.cancelSubscription(userId, subscriptionNumber, 'cancellationReason'); const postParams = instance.requestPost.getCall(0).args[0]; expect(postParams.body.refundType).to.equal('ELECTRONIC'); }); it('passes refundType to requestPost if provided', async () => { const refundType = 'someType'; await instance.cancelSubscription(userId, subscriptionNumber, cancellationReason, false, null, false, 'cancellationType', refundType); const postParams = instance.requestPost.getCall(0).args[0]; expect(postParams.body.refundType).to.equal('someType'); }); }); describe('undoCancelSubscription', () => { const userId = 'abc'; const subscriptionNumber = 'subscription-number'; const mockResponse = { status: 200, }; beforeEach(() => { instance.requestDelete.resolves(mockResponse); }); it('should pass correct parameters to requestDelete', async () => { const url = `${instance.membershipApi}/subs/transitions/subscriptions/cancel/${subscriptionNumber}`; await instance.undoCancelSubscription(userId, subscriptionNumber, false); const postParams = instance.requestDelete.getCall(0).args[0]; // Path checks expect(postParams).to.have.property('url'); expect(postParams.url).to.equal(url); // Other checks expect(postParams).to.have.property('additionalHeaders'); }); it('when user ID is not set it throws a ValidationError', async () => { try { await instance.undoCancelSubscription(null, subscriptionNumber, false); expect.fail('Should have thrown the error!'); } catch (error) { expect(error.message).to.equal('User ID is not defined'); } }); it('when subscription number is not set it throws a ValidationError', async () => { try { await instance.undoCancelSubscription(userId, null, false); expect.fail('Should have thrown the error!'); } catch (error) { expect(error.message).to.equal('Subscription Number is not defined'); } }); context('when no errors are returned', () => { beforeEach(() => { instance.requestDelete.resolves(mockResponse); }); it('returns the result', async () => { const data = await instance.undoCancelSubscription(userId, subscriptionNumber, false); expect(data).to.deep.equal({ ...mockResponse, success: true }); }); }); context('when an empty response is returned with http 200', () => { it('should throw an error', async () => { instance.requestDelete.resolves(null); try { await instance.undoCancelSubscription(userId, subscriptionNumber, false); expect.fail('Should have thrown the error!'); } catch (error) { expect(error.message).to.equal('Empty UNDO subscription cancellation response.'); expect(error.data).to.deep.equal({ userId: userId, additionalHeaders: { 'Cache-Control': 'no-cache', }, subscriptionNumber, isCustomerServiceAgent: false, response: null }); } }); }); context('when the response is returned with success: false', () => { it('should throw an error', async () => { instance.requestDelete.resolves({ success: false }); try { await instance.undoCancelSubscription(userId, subscriptionNumber, false); expect.fail('Should have thrown the error!'); } catch (error) { expect(error.message).to.equal('Response returned with an unsuccessful result.'); expect(error.data).to.deep.equal({ userId: userId, additionalHeaders: { 'Cache-Control': 'no-cache', }, subscriptionNumber, isCustomerServiceAgent: false, response: { success: false } }); } }); }); context('when an error is thrown', () => { const errorResponse = new Error('Test failure'); beforeEach(() => { instance.requestDelete.rejects(errorResponse); }); it('it throws the same error', async () => { try { await instance.undoCancelSubscription(userId, subscriptionNumber, false); expect.fail('Should have thrown the error!'); } catch (error) { expect(error).to.deep.equal(errorResponse); } }); }); }); describe('undoStepUp', () => { const subscriptionNumber = 'subscription-number'; const mockResponse = { status: 200, subscriptionAfter: subscriptionTransition, }; beforeEach(() => { instance.requestDelete.resolves(mockResponse); }); it('should pass correct parameters to requestDelete', async () => { const url = `${instance.membershipApi}/subs/transitions/subscriptions/stepup/${subscriptionNumber}`; await instance.undoStepUp(subscriptionNumber); const deleteParams = instance.requestDelete.getCall(0).args[0]; // Path checks expect(deleteParams).to.have.property('url'); expect(deleteParams.url).to.equal(url); }); it('should return data on success', async () => { const result = await instance.undoStepUp(subscriptionNumber); expect(result).to.deep.equal({ ...mockResponse, success: true }); }); it('should throw an error when the response is returned with success: false', async () => { instance.requestDelete.resolves({ status: 400 }); try { await instance.undoStepUp(subscriptionNumber); expect.fail('Should have thrown the error!'); } catch (error) { expect(error.message).to.equal('Response returned with an unsuccessful result.'); expect(error.data).to.deep.equal({ subscriptionNumber, response: { status: 400 } }); } }); it('when subscription number is not set it throws a ValidationError', async () => { try { await instance.undoStepUp(null); expect.fail('Should have thrown the error!'); } catch (error) { expect(error.message).to.equal('Subscription Number is not defined'); } }); }); describe('getDowngradeOptions', () => { const subscriptionNumber = 'subscription-number'; const mockResponse = { offerId: 'some-offer-id', friendlyName: 'Really Friendly Name', percent: 25 }; beforeEach(() => { instance.requestGet.resolves(mockResponse); }); it('should pass correct parameters to requestGet', async () => { const url = `${instance.membershipApi}/subs/transitions/subscriptions/downgrade/options/${subscriptionNumber}?checkForStepUpOptions=false`; await instance.getDowngradeOptions(subscriptionNumber); const getParams = instance.requestGet.getCall(0).args[0]; // Path checks expect(getParams).to.have.property('url'); expect(getParams.url).to.equal(url); }); it('should allow fetching stepup options', async () => { const url = `${instance.membershipApi}/subs/transitions/subscriptions/downgrade/options/${subscriptionNumber}?checkForStepUpOptions=true`; await instance.getDowngradeOptions(subscriptionNumber, true); const getParams = instance.requestGet.getCall(0).args[0]; // Path checks expect(getParams).to.have.property('url'); expect(getParams.url).to.equal(url); }); it('should throw an error when the request fails', async () => { instance.requestGet.rejects(new Error('oops')); try { await instance.getDowngradeOptions(subscriptionNumber); expect.fail('Should have thrown the error!'); } catch (error) { expect(error.message).to.equal('oops'); } }); }); });