should = require('chai').should() sinon = require 'sinon' crypto = require 'crypto' querystring = require 'querystring' RecurlyWrapper = require "../../../../app/js/Features/Subscription/RecurlyWrapper" Settings = require "settings-sharelatex" tk = require("timekeeper") fixtures = "subscriptions/44f83d7cba354d5b84812419f923ea96": "" + "" + " " + " " + " gold" + " Gold plan" + " " + " 44f83d7cba354d5b84812419f923ea96" + " active" + " 800" + " EUR" + " 1" + " 2011-05-27T07:00:00Z" + " " + " " + " 2011-06-27T07:00:00Z" + " 2011-07-27T07:00:00Z" + " " + " " + " " + " " + " ipaddresses" + " 10" + " 150" + " " + " " + " " + " " + " " + "" "recurly_js/result/70db44b10f5f4b238669480c9903f6f5": "" + "" + " " + " " + " gold" + " Gold plan" + " " + " 44f83d7cba354d5b84812419f923ea96" + " active" + " 800" + " EUR" + " 1" + " 2011-05-27T07:00:00Z" + " " + " " + " 2011-06-27T07:00:00Z" + " 2011-07-27T07:00:00Z" + " " + " " + " " + " " + " ipaddresses" + " 10" + " 150" + " " + " " + " " + " " + " " + "" "accounts/104": "" + "" + " " + " " + " " + " " + " " + " " + " 104" + " active" + " " + " verena@example.com" + " Verena" + " Example" + " " + " a92468579e9c4231a6c0031c4716c01d" + " 2011-10-25T12:00:00" + "" mockApiRequest = (options, callback) -> if fixtures[options.url] callback(null, {statusCode : 200}, fixtures[options.url]) else callback("Not found", {statusCode : 404}) describe "RecurlyWrapper", -> beforeEach -> Settings.plans = [{ planCode: "collaborator" name: "Collaborator" features: collaborators: -1 versioning: true }] Settings.defaultPlanCode = collaborators: 0 versioning: false describe "sign", -> before (done) -> tk.freeze Date.now() # freeze the time for these tests RecurlyWrapper.sign({ subscription : plan_code : "gold" name : "$$$" }, (error, signature) => @signature = signature done() ) after -> tk.reset() it "should be signed correctly", -> signed = @signature.split("|")[0] query = @signature.split("|")[1] crypto.createHmac("sha1", Settings.apis.recurly.privateKey).update(query).digest("hex").should.equal signed it "should be url escaped", -> query = @signature.split("|")[1] should.equal query.match(/\[/), null query.match(/\%5B/).should.not.equal null it "should contain the passed data", -> query = querystring.parse @signature.split("|")[1] query["subscription[plan_code]"].should.equal "gold" query["subscription[name]"].should.equal "$$$" it "should contain a nonce", -> query = querystring.parse @signature.split("|")[1] should.exist query["nonce"] it "should contain a timestamp", -> query = querystring.parse @signature.split("|")[1] query["timestamp"].should.equal Math.round(Date.now() / 1000) + "" describe "_parseXml", -> it "should convert different data types into correct representations", (done) -> xml = "" + "" + " " + " " + " gold" + " Gold plan" + " " + " 44f83d7cba354d5b84812419f923ea96" + " active" + " 800" + " EUR" + " 1" + " 2011-05-27T07:00:00Z" + " " + " " + " 2011-06-27T07:00:00Z" + " 2011-07-27T07:00:00Z" + " " + " " + " " + " " + " ipaddresses" + " 10" + " 150" + " " + " " + " " + " " + " " + "" RecurlyWrapper._parseXml xml, (error, data) -> data.subscription.plan.plan_code.should.equal "gold" data.subscription.plan.name.should.equal "Gold plan" data.subscription.uuid.should.equal "44f83d7cba354d5b84812419f923ea96" data.subscription.state.should.equal "active" data.subscription.unit_amount_in_cents.should.equal 800 data.subscription.currency.should.equal "EUR" data.subscription.quantity.should.equal 1 data.subscription.activated_at.should.deep.equal new Date("2011-05-27T07:00:00Z") should.equal data.subscription.canceled_at, null should.equal data.subscription.expires_at, null data.subscription.current_period_started_at.should.deep.equal new Date("2011-06-27T07:00:00Z") data.subscription.current_period_ends_at.should.deep.equal new Date("2011-07-27T07:00:00Z") should.equal data.subscription.trial_started_at, null should.equal data.subscription.trial_ends_at, null data.subscription.subscription_add_ons.should.deep.equal [{ add_on_code: "ipaddresses" quantity: "10" unit_amount_in_cents: "150" }] data.subscription.account.url.should.equal "https://api.recurly.com/v2/accounts/1" data.subscription.url.should.equal "https://api.recurly.com/v2/subscriptions/44f83d7cba354d5b84812419f923ea96" data.subscription.plan.url.should.equal "https://api.recurly.com/v2/plans/gold" done() describe "getSubscription", -> describe "with proper subscription id", -> before -> @apiRequest = sinon.stub(RecurlyWrapper, "apiRequest", mockApiRequest) RecurlyWrapper.getSubscription "44f83d7cba354d5b84812419f923ea96", (error, recurlySubscription) => @recurlySubscription = recurlySubscription after -> RecurlyWrapper.apiRequest.restore() it "should look up the subscription at the normal API end point", -> @apiRequest.args[0][0].url.should.equal "subscriptions/44f83d7cba354d5b84812419f923ea96" it "should return the subscription", -> @recurlySubscription.uuid.should.equal "44f83d7cba354d5b84812419f923ea96" describe "with ReculyJS token", -> before -> @apiRequest = sinon.stub(RecurlyWrapper, "apiRequest", mockApiRequest) RecurlyWrapper.getSubscription "70db44b10f5f4b238669480c9903f6f5", {recurlyJsResult: true}, (error, recurlySubscription) => @recurlySubscription = recurlySubscription after -> RecurlyWrapper.apiRequest.restore() it "should return the subscription", -> @recurlySubscription.uuid.should.equal "44f83d7cba354d5b84812419f923ea96" it "should look up the subscription at the RecurlyJS API end point", -> @apiRequest.args[0][0].url.should.equal "recurly_js/result/70db44b10f5f4b238669480c9903f6f5" describe "with includeAccount", -> beforeEach -> @apiRequest = sinon.stub(RecurlyWrapper, "apiRequest", mockApiRequest) RecurlyWrapper.getSubscription "44f83d7cba354d5b84812419f923ea96", {includeAccount: true}, (error, recurlySubscription) => @recurlySubscription = recurlySubscription afterEach -> RecurlyWrapper.apiRequest.restore() it "should request the account from the API", -> @apiRequest.args[1][0].url.should.equal "accounts/104" it "should populate the account attribute", -> @recurlySubscription.account.account_code.should.equal "104" describe "updateSubscription", -> beforeEach (done) -> @recurlySubscriptionId = "subscription-id-123" @apiRequest = sinon.stub RecurlyWrapper, "apiRequest", (options, callback) => @requestOptions = options callback null, {}, fixtures["subscriptions/44f83d7cba354d5b84812419f923ea96"] RecurlyWrapper.updateSubscription @recurlySubscriptionId, { plan_code : "silver", timeframe: "now" }, (error, recurlySubscription) => @recurlySubscription = recurlySubscription done() afterEach -> RecurlyWrapper.apiRequest.restore() it "should send an update request to the API", -> @apiRequest.called.should.equal true @requestOptions.body.should.equal """ silver now """ @requestOptions.url.should.equal "subscriptions/#{@recurlySubscriptionId}" @requestOptions.method.should.equal "put" it "should return the updated subscription", -> should.exist @recurlySubscription @recurlySubscription.plan.plan_code.should.equal "gold" describe "cancelSubscription", -> beforeEach (done) -> @recurlySubscriptionId = "subscription-id-123" @apiRequest = sinon.stub RecurlyWrapper, "apiRequest", (options, callback) => options.url.should.equal "subscriptions/#{@recurlySubscriptionId}/cancel" options.method.should.equal "put" callback() RecurlyWrapper.cancelSubscription(@recurlySubscriptionId, done) afterEach -> RecurlyWrapper.apiRequest.restore() it "should send a cancel request to the API", -> @apiRequest.called.should.equal true describe "reactivateSubscription", -> beforeEach (done) -> @recurlySubscriptionId = "subscription-id-123" @apiRequest = sinon.stub RecurlyWrapper, "apiRequest", (options, callback) => options.url.should.equal "subscriptions/#{@recurlySubscriptionId}/reactivate" options.method.should.equal "put" callback() RecurlyWrapper.reactivateSubscription(@recurlySubscriptionId, done) afterEach -> RecurlyWrapper.apiRequest.restore() it "should send a cancel request to the API", -> @apiRequest.called.should.equal true describe "redeemCoupon", -> beforeEach (done) -> @recurlyAccountId = "account-id-123" @coupon_code = "312321312" @apiRequest = sinon.stub RecurlyWrapper, "apiRequest", (options, callback) => options.url.should.equal "coupons/#{@coupon_code}/redeem" options.body.indexOf("#{@recurlyAccountId}").should.not.equal -1 options.body.indexOf("USD").should.not.equal -1 options.method.should.equal "post" callback() RecurlyWrapper.redeemCoupon(@recurlyAccountId, @coupon_code, done) afterEach -> RecurlyWrapper.apiRequest.restore() it "should send the request to redem the coupon", -> @apiRequest.called.should.equal true