~singpolyma/sgx-jmp

934772529eecfb46e4a879824cec6e43e4872fce — Stephen Paul Weber 1 year, 3 months ago 56f62d1
Customer always has a JID
M lib/customer.rb => lib/customer.rb +12 -21
@@ 14,7 14,7 @@ require_relative "./sip_account"
class Customer
	extend Forwardable

	attr_reader :customer_id, :balance
	attr_reader :customer_id, :balance, :jid
	def_delegators :@plan, :active?, :activate_plan_starting_now, :bill_plan,
	               :currency, :merchant_account, :plan_name, :auto_top_up_amount
	def_delegators :@sgx, :register!, :registered?, :fwd_timeout=


@@ 22,17 22,15 @@ class Customer

	def initialize(
		customer_id,
		plan_name: nil,
		expires_at: Time.now,
		jid,
		plan: CustomerPlan.new(customer_id),
		balance: BigDecimal.new(0),
		sgx: BackendSgx.new(customer_id)
	)
		@plan = CustomerPlan.new(
			customer_id,
			plan: plan_name && Plan.for(plan_name), expires_at: expires_at
		)
		@plan = plan
		@usage = CustomerUsage.new(customer_id)
		@customer_id = customer_id
		@jid = jid
		@balance = balance
		@sgx = sgx
	end


@@ 40,9 38,10 @@ class Customer
	def with_plan(plan_name)
		self.class.new(
			@customer_id,
			@jid,
			plan: @plan.with_plan_name(plan_name),
			balance: @balance,
			expires_at: expires_at,
			plan_name: plan_name
			sgx: @sgx
		)
	end



@@ 54,19 53,11 @@ class Customer
			.then(PaymentMethods.method(:for_braintree_customer))
	end

	def jid
		@jid ||= REDIS.get("jmp_customer_jid-#{customer_id}").then do |sjid|
			Blather::JID.new(sjid)
		end
	end

	def stanza_to(stanza)
		jid.then do |jid|
			stanza = stanza.dup
			stanza.to = jid.with(resource: stanza.to&.resource)
			stanza.from = stanza.from.with(domain: CONFIG[:component][:jid])
			BLATHER << stanza
		end
		stanza = stanza.dup
		stanza.to = jid.with(resource: stanza.to&.resource)
		stanza.from = stanza.from.with(domain: CONFIG[:component][:jid])
		BLATHER << stanza
	end

	def stanza_from(stanza)

M lib/customer_plan.rb => lib/customer_plan.rb +10 -0
@@ 2,6 2,8 @@

require "forwardable"

require_relative "em"

class CustomerPlan
	extend Forwardable



@@ 19,6 21,14 @@ class CustomerPlan
		plan_name && @expires_at > Time.now
	end

	def with_plan_name(plan_name)
		self.class.new(
			@customer_id,
			plan: Plan.for(plan_name),
			expires_at: @expires_at
		)
	end

	def auto_top_up_amount
		REDIS.get("jmp_customer_auto_top_up_amount-#{@customer_id}").then(&:to_i)
	end

M lib/customer_repo.rb => lib/customer_repo.rb +30 -9
@@ 1,6 1,7 @@
# frozen_string_literal: true

require_relative "customer"
require_relative "polyfill"

class CustomerRepo
	def initialize(redis: REDIS, db: DB, braintree: BRAINTREE)


@@ 10,20 11,16 @@ class CustomerRepo
	end

	def find(customer_id)
		result = @db.query_defer(<<~SQL, [customer_id])
			SELECT COALESCE(balance,0) AS balance, plan_name, expires_at
			FROM customer_plans LEFT JOIN balances USING (customer_id)
			WHERE customer_id=$1 LIMIT 1
		SQL
		result.then do |rows|
			Customer.new(customer_id, **rows.first&.transform_keys(&:to_sym) || {})
		@redis.get("jmp_customer_jid-#{customer_id}").then do |jid|
			raise "No jid" unless jid
			find_inner(customer_id, jid)
		end
	end

	def find_by_jid(jid)
		@redis.get("jmp_customer_id-#{jid}").then do |customer_id|
			raise "No customer id" unless customer_id
			find(customer_id)
			find_inner(customer_id, jid)
		end
	end



@@ 35,8 32,32 @@ class CustomerRepo
				"jmp_customer_id-#{jid}", cid, "jmp_customer_jid-#{cid}", jid
			).then do |redis_result|
				raise "Saving new customer to redis failed" unless redis_result == 1
				Customer.new(cid)
				Customer.new(cid, Blather::JID.new(jid))
			end
		end
	end

protected

	def hydrate_plan(customer_id, raw_customer)
		raw_customer.dup.tap do |data|
			data[:plan] = CustomerPlan.new(
				customer_id,
				plan: data.delete(:plan_name)&.then(&Plan.method(:for)),
				expires_at: data.delete(:expires_at)
			)
		end
	end

	def find_inner(customer_id, jid)
		result = @db.query_defer(<<~SQL, [customer_id])
			SELECT COALESCE(balance,0) AS balance, plan_name, expires_at
			FROM customer_plans LEFT JOIN balances USING (customer_id)
			WHERE customer_id=$1 LIMIT 1
		SQL
		result.then do |rows|
			data = hydrate_plan(customer_id, rows.first&.transform_keys(&:to_sym) || {})
			Customer.new(customer_id, Blather::JID.new(jid), **data)
		end
	end
end

M lib/registration.rb => lib/registration.rb +2 -4
@@ 13,12 13,11 @@ require_relative "./web_register_manager"

class Registration
	def self.for(customer, web_register_manager)
		jid = Command.execution.iq.from.stripped
		customer.registered?.then do |registered|
			if registered
				Registered.new(registered.phone)
			else
				web_register_manager[jid].choose_tel.then do |tel|
				web_register_manager[customer.jid].choose_tel.then do |tel|
					Activation.for(customer, tel)
				end
			end


@@ 444,8 443,7 @@ class Registration
	protected

		def cheogram_sip_addr
			jid = Command.execution.iq.from.stripped
			"sip:#{ERB::Util.url_encode(jid)}@sip.cheogram.com"
			"sip:#{ERB::Util.url_encode(@customer.jid)}@sip.cheogram.com"
		end

		def raise_setup_error(e)

M lib/sip_account.rb => lib/sip_account.rb +1 -0
@@ 1,5 1,6 @@
# frozen_string_literal: true

require "em-synchrony/em-http" # For aget vs get
require "securerandom"
require "value_semantics/monkey_patched"


M sgx_jmp.rb => sgx_jmp.rb +4 -4
@@ 241,10 241,10 @@ message(

	CustomerRepo.new.find(
		Blather::JID.new(address["jid"].to_s).node.delete_prefix("customer_")
	).then(&:jid).then { |customer_jid|
	).then { |customer|
		m.from = m.from.with(domain: CONFIG[:component][:jid])
		m.to = m.to.with(domain: customer_jid.domain)
		address["jid"] = customer_jid.to_s
		m.to = m.to.with(domain: customer.jid.domain)
		address["jid"] = customer.jid.to_s
		BLATHER << m
	}.catch { |e| panic(e, sentry_hub) }
end


@@ 416,7 416,7 @@ Command.new(
) {
	Command.customer.then do |customer|
		url = CONFIG[:credit_card_url].call(
			reply.to.stripped.to_s.gsub("\\", "%5C"),
			customer.jid.to_s.gsub("\\", "%5C"),
			customer.customer_id
		)
		desc = "Manage credits cards and settings"

M test/test_add_bitcoin_address.rb => test/test_add_bitcoin_address.rb +8 -8
@@ 7,23 7,23 @@ require "add_bitcoin_address"
class AddBitcoinAddressTest < Minitest::Test
	def test_for
		iq = Blather::Stanza::Iq::Command.new
		customer = Customer.new("test")
		AddBitcoinAddress.for(iq, AltTopUpForm.new(customer), customer)
		cust = customer
		AddBitcoinAddress.for(iq, AltTopUpForm.new(cust), cust)
	end

	def test_for_add_bitcoin
		iq = Blather::Stanza::Iq::Command.new
		iq.form.fields = [{ var: "add_btc_address", value: "true" }]
		customer = Customer.new("test")
		AddBitcoinAddress.for(iq, AltTopUpForm.new(customer), customer)
		cust = customer
		AddBitcoinAddress.for(iq, AltTopUpForm.new(cust), cust)
	end

	def test_write
		customer = Minitest::Mock.new
		customer.expect(:add_btc_address, EMPromise.resolve("newaddress"))
		cust = Minitest::Mock.new
		cust.expect(:add_btc_address, EMPromise.resolve("newaddress"))
		iq = Blather::Stanza::Iq::Command.new
		AddBitcoinAddress.new(iq, customer).write.sync
		assert_mock customer
		AddBitcoinAddress.new(iq, cust).write.sync
		assert_mock cust
	end
	em :test_write


M test/test_alt_top_up_form.rb => test/test_alt_top_up_form.rb +9 -9
@@ 13,7 13,7 @@ class AltTopUpFormTest < Minitest::Test
		)
		assert_kind_of(
			AltTopUpForm,
			AltTopUpForm.for(Customer.new("test")).sync
			AltTopUpForm.for(customer).sync
		)
	end
	em :test_for


@@ 26,7 26,7 @@ class AltTopUpFormTest < Minitest::Test
		)
		assert_kind_of(
			AltTopUpForm,
			AltTopUpForm.for(Customer.new("test")).sync
			AltTopUpForm.for(customer).sync
		)
	end
	em :test_for_addresses


@@ 39,7 39,7 @@ class AltTopUpFormTest < Minitest::Test
		)
		assert_kind_of(
			AltTopUpForm,
			AltTopUpForm.for(Customer.new("test", plan_name: "test_cad")).sync
			AltTopUpForm.for(customer(plan_name: "test_cad")).sync
		)
	end
	em :test_for_cad


@@ 48,7 48,7 @@ class AltTopUpFormTest < Minitest::Test
		assert_kind_of(
			Blather::Stanza::X,
			AltTopUpForm.new(
				Customer.new("test"),
				customer,
				AltTopUpForm::AddBtcAddressField.new
			).form
		)


@@ 58,7 58,7 @@ class AltTopUpFormTest < Minitest::Test
		assert_kind_of(
			Blather::Stanza::X,
			AltTopUpForm.new(
				Customer.new("test"),
				customer,
				AltTopUpForm::AddBtcAddressField::AddNewBtcAddressField.new
			).form
		)


@@ 69,7 69,7 @@ class AltTopUpFormTest < Minitest::Test
		iq_form.fields = [
			{ var: "add_btc_address", value: "true" }
		]
		assert AltTopUpForm.new(Customer.new("t")).parse(iq_form)[:add_btc_address]
		assert AltTopUpForm.new(customer).parse(iq_form)[:add_btc_address]
	end

	def test_parse_1


@@ 77,7 77,7 @@ class AltTopUpFormTest < Minitest::Test
		iq_form.fields = [
			{ var: "add_btc_address", value: "1" }
		]
		assert AltTopUpForm.new(Customer.new("t")).parse(iq_form)[:add_btc_address]
		assert AltTopUpForm.new(customer).parse(iq_form)[:add_btc_address]
	end

	def test_parse_false


@@ 85,11 85,11 @@ class AltTopUpFormTest < Minitest::Test
		iq_form.fields = [
			{ var: "add_btc_address", value: "false" }
		]
		refute AltTopUpForm.new(Customer.new("t")).parse(iq_form)[:add_btc_address]
		refute AltTopUpForm.new(customer).parse(iq_form)[:add_btc_address]
	end

	def test_parse_not_presend
		iq_form = Blather::Stanza::X.new
		refute AltTopUpForm.new(Customer.new("t")).parse(iq_form)[:add_btc_address]
		refute AltTopUpForm.new(customer).parse(iq_form)[:add_btc_address]
	end
end

M test/test_buy_account_credit_form.rb => test/test_buy_account_credit_form.rb +1 -1
@@ 24,7 24,7 @@ class BuyAccountCreditFormTest < Minitest::Test

		assert_kind_of(
			BuyAccountCreditForm,
			BuyAccountCreditForm.for(Customer.new("test")).sync
			BuyAccountCreditForm.for(customer).sync
		)
	end
	em :test_for

M test/test_customer.rb => test/test_customer.rb +12 -30
@@ 43,7 43,7 @@ class CustomerTest < Minitest::Test
			OpenStruct.new(cmd_tuples: 1),
			[String, ["test", "test_usd"]]
		)
		Customer.new("test", plan_name: "test_usd").bill_plan.sync
		customer(plan_name: "test_usd").bill_plan.sync
		CustomerPlan::DB.verify
	end
	em :test_bill_plan_activate


@@ 71,43 71,25 @@ class CustomerTest < Minitest::Test
			[String, ["test", "test_usd"]]
		)
		CustomerPlan::DB.expect(:exec, nil, [String, ["test"]])
		Customer.new("test", plan_name: "test_usd").bill_plan.sync
		customer(plan_name: "test_usd").bill_plan.sync
		CustomerPlan::DB.verify
	end
	em :test_bill_plan_update

	def test_jid
		Customer::REDIS.expect(
			:get,
			EMPromise.resolve("test@example.com"),
			["jmp_customer_jid-test"]
		)
		jid = Customer.new("test").jid.sync
		assert_kind_of Blather::JID, jid
		assert_equal "test@example.com", jid.to_s
		Customer::REDIS.verify
	end
	em :test_jid

	def test_stanza_to
		Customer::REDIS.expect(
			:get,
			EMPromise.resolve("test@example.com"),
			["jmp_customer_jid-test"]
		)
		Customer::BLATHER.expect(
			:<<,
			nil,
			[Matching.new do |stanza|
				assert_equal "+15555550000@component/a", stanza.from.to_s
				assert_equal "test@example.com/b", stanza.to.to_s
				assert_equal "test@example.net/b", stanza.to.to_s
			end]
		)
		m = Blather::Stanza::Message.new
		m.from = "+15555550000@sgx/a"
		m.to = "customer_test@component/b"
		Customer.new("test").stanza_to(m).sync
		Customer::BLATHER.verify
		customer.stanza_to(m)
		assert_mock Customer::BLATHER
	end
	em :test_stanza_to



@@ 123,7 105,7 @@ class CustomerTest < Minitest::Test
		m = Blather::Stanza::Message.new
		m.from = "test@example.com/a"
		m.to = "+15555550000@component/b"
		Customer.new("test").stanza_from(m)
		customer.stanza_from(m)
		Customer::BLATHER.verify
	end



@@ 149,7 131,7 @@ class CustomerTest < Minitest::Test
				},
				Date.today => 123
			),
			Customer.new("test").usage_report(report_for).sync
			customer.usage_report(report_for).sync
		)
	end
	em :test_customer_usage_report


@@ 164,7 146,7 @@ class CustomerTest < Minitest::Test
				"Authorization" => "Basic Y2F0YXB1bHRfdG9rZW46Y2F0YXB1bHRfc2VjcmV0"
			}
		).to_return(status: 404)
		sip = Customer.new("test").sip_account.sync
		sip = customer.sip_account.sync
		assert_kind_of SipAccount::New, sip
		assert_equal "test", sip.username
		assert_requested req


@@ 196,7 178,7 @@ class CustomerTest < Minitest::Test
			{ name: "test", domainId: "domain", id: "endpoint" }
		].to_json)

		sip = Customer.new("test").sip_account.sync
		sip = customer.sip_account.sync
		assert_kind_of SipAccount, sip
		assert_equal "test", sip.username
		assert_equal(


@@ 218,7 200,7 @@ class CustomerTest < Minitest::Test
		).to_return(status: 400)

		assert_raises(RuntimeError) do
			Customer.new("test").sip_account.sync
			customer.sip_account.sync
		end
	end
	em :test_sip_account_error


@@ 229,7 211,7 @@ class CustomerTest < Minitest::Test
			EMPromise.resolve(["testaddr"]),
			["jmp_customer_btc_addresses-test"]
		)
		assert_equal ["testaddr"], Customer.new("test").btc_addresses.sync
		assert_equal ["testaddr"], customer.btc_addresses.sync
		assert_mock Customer::REDIS
	end
	em :test_btc_addresses


@@ 245,7 227,7 @@ class CustomerTest < Minitest::Test
			EMPromise.resolve(nil),
			["testaddr", "http://notify.example.com"]
		)
		assert_equal "testaddr", Customer.new("test").add_btc_address.sync
		assert_equal "testaddr", customer.add_btc_address.sync
		assert_mock Customer::REDIS
		assert_mock Customer::ELECTRUM
	end

M test/test_customer_repo.rb => test/test_customer_repo.rb +7 -1
@@ 52,7 52,13 @@ class CustomerRepoTest < Minitest::Test

	def test_find_db_empty
		db = Minitest::Mock.new
		repo = mkrepo(db: db)
		redis = Minitest::Mock.new
		redis.expect(
			:get,
			EMPromise.resolve("test@example.net"),
			["jmp_customer_jid-7357"]
		)
		repo = mkrepo(db: db, redis: redis)
		db.expect(
			:query_defer,
			EMPromise.resolve([]),

M test/test_helper.rb => test/test_helper.rb +15 -0
@@ 38,6 38,21 @@ require "backend_sgx"
$VERBOSE = nil
Sentry.init

def customer(customer_id="test", plan_name: nil, **kwargs)
	jid = Blather::JID.new("#{customer_id}@example.net")
	if plan_name
		expires_at = kwargs.delete(:expires_at) || Time.now
		plan = CustomerPlan.new(
			customer_id,
			plan: Plan.for(plan_name),
			expires_at: expires_at
		)
		Customer.new(customer_id, jid, plan: plan, **kwargs)
	else
		Customer.new(customer_id, jid, **kwargs)
	end
end

CONFIG = {
	sgx: "sgx",
	component: {

M test/test_low_balance.rb => test/test_low_balance.rb +4 -4
@@ 14,7 14,7 @@ class LowBalanceTest < Minitest::Test
			EMPromise.resolve(1),
			["jmp_low_balance_notify-test"]
		)
		assert_kind_of LowBalance::Locked, LowBalance.for(Customer.new("test")).sync
		assert_kind_of LowBalance::Locked, LowBalance.for(customer).sync
	end
	em :test_for_locked



@@ 41,7 41,7 @@ class LowBalanceTest < Minitest::Test
		)
		assert_kind_of(
			LowBalance,
			LowBalance.for(Customer.new("test")).sync
			LowBalance.for(customer).sync
		)
		assert_mock ExpiringLock::REDIS
	end


@@ 65,7 65,7 @@ class LowBalanceTest < Minitest::Test
		)
		assert_kind_of(
			LowBalance::AutoTopUp,
			LowBalance.for(Customer.new("test")).sync
			LowBalance.for(customer).sync
		)
		assert_mock ExpiringLock::REDIS
	end


@@ 75,7 75,7 @@ class LowBalanceTest < Minitest::Test
		LowBalance::AutoTopUp::Transaction = Minitest::Mock.new

		def setup
			@customer = Customer.new("test")
			@customer = customer
			@auto_top_up = LowBalance::AutoTopUp.new(@customer, 100)
		end


M test/test_registration.rb => test/test_registration.rb +22 -25
@@ 26,7 26,7 @@ class RegistrationTest < Minitest::Test
		iq.from = "test@example.com"
		result = execute_command(iq) do
			Registration.for(
				Customer.new("test", sgx: sgx),
				customer(sgx: sgx),
				Minitest::Mock.new
			)
		end


@@ 35,15 35,12 @@ class RegistrationTest < Minitest::Test
	em :test_for_registered

	def test_for_activated
		sgx = OpenStruct.new(registered?: EMPromise.resolve(nil))
		web_manager = WebRegisterManager.new
		web_manager["test@example.com"] = "+15555550000"
		iq = Blather::Stanza::Iq::Command.new
		iq.from = "test@example.com"
		result = execute_command(iq) do
		web_manager["test@example.net"] = "+15555550000"
		result = execute_command do
			sgx = OpenStruct.new(registered?: EMPromise.resolve(nil))
			Registration.for(
				Customer.new(
					"test",
				customer(
					plan_name: "test_usd",
					expires_at: Time.now + 999,
					sgx: sgx


@@ 58,12 55,12 @@ class RegistrationTest < Minitest::Test
	def test_for_not_activated_with_customer_id
		sgx = OpenStruct.new(registered?: EMPromise.resolve(nil))
		web_manager = WebRegisterManager.new
		web_manager["test@example.com"] = "+15555550000"
		web_manager["test@example.net"] = "+15555550000"
		iq = Blather::Stanza::Iq::Command.new
		iq.from = "test@example.com"
		result = execute_command(iq) do
			Registration.for(
				Customer.new("test", sgx: sgx),
				customer(sgx: sgx),
				web_manager
			)
		end


@@ 121,8 118,8 @@ class RegistrationTest < Minitest::Test
		Customer::BRAINTREE = Minitest::Mock.new

		def test_for_bitcoin
			customer = Minitest::Mock.new(Customer.new("test"))
			customer.expect(
			cust = Minitest::Mock.new(customer)
			cust.expect(
				:add_btc_address,
				EMPromise.resolve("testaddr")
			)


@@ 131,7 128,7 @@ class RegistrationTest < Minitest::Test
				{ var: "activation_method", value: "bitcoin" },
				{ var: "plan_name", value: "test_usd" }
			]
			result = Registration::Payment.for(iq, customer, "+15555550000")
			result = Registration::Payment.for(iq, cust, "+15555550000")
			assert_kind_of Registration::Payment::Bitcoin, result
		end



@@ 154,7 151,7 @@ class RegistrationTest < Minitest::Test
			]
			result = Registration::Payment.for(
				iq,
				Customer.new("test"),
				customer,
				"+15555550000"
			).sync
			assert_kind_of Registration::Payment::CreditCard, result


@@ 169,7 166,7 @@ class RegistrationTest < Minitest::Test
			]
			result = Registration::Payment.for(
				iq,
				Customer.new("test"),
				customer,
				"+15555550000"
			)
			assert_kind_of Registration::Payment::InviteCode, result


@@ 181,7 178,7 @@ class RegistrationTest < Minitest::Test

			def setup
				@customer = Minitest::Mock.new(
					Customer.new("test", plan_name: "test_usd")
					customer(plan_name: "test_usd")
				)
				@customer.expect(
					:add_btc_address,


@@ 233,13 230,13 @@ class RegistrationTest < Minitest::Test
		class CreditCardTest < Minitest::Test
			def setup
				@credit_card = Registration::Payment::CreditCard.new(
					Customer.new("test"),
					customer,
					"+15555550000"
				)
			end

			def test_for
				customer = Minitest::Mock.new(Customer.new("test"))
				customer = Minitest::Mock.new(customer)
				customer.expect(
					:payment_methods,
					EMPromise.resolve(OpenStruct.new(default_payment_method: :test))


@@ 291,7 288,7 @@ class RegistrationTest < Minitest::Test
					EMPromise.resolve(nil)
				)
				customer = Minitest::Mock.new(
					Customer.new("test", plan_name: "test_usd")
					customer(plan_name: "test_usd")
				)
				Registration::Payment::CreditCard::Activate::Transaction.expect(
					:sale,


@@ 323,7 320,7 @@ class RegistrationTest < Minitest::Test

			def test_write_declines
				customer = Minitest::Mock.new(
					Customer.new("test", plan_name: "test_usd")
					customer(plan_name: "test_usd")
				)
				iq = Blather::Stanza::Iq::Command.new
				iq.from = "test@example.com"


@@ 370,7 367,7 @@ class RegistrationTest < Minitest::Test
			Registration::Payment::InviteCode::Finish =
				Minitest::Mock.new
			def test_write
				customer = Customer.new("test", plan_name: "test_usd")
				customer = customer(plan_name: "test_usd")
				Registration::Payment::InviteCode::DB.expect(:transaction, true, [])
				Registration::Payment::InviteCode::Finish.expect(
					:new,


@@ 413,7 410,7 @@ class RegistrationTest < Minitest::Test

			def test_write_bad_code
				result = execute_command do
					customer = Customer.new("test", plan_name: "test_usd")
					customer = customer(plan_name: "test_usd")
					Registration::Payment::InviteCode::REDIS.expect(
						:get,
						EMPromise.resolve(0),


@@ 467,7 464,7 @@ class RegistrationTest < Minitest::Test

			def test_write_bad_code_over_limit
				result = execute_command do
					customer = Customer.new("test", plan_name: "test_usd")
					customer = customer(plan_name: "test_usd")
					Registration::Payment::InviteCode::REDIS.expect(
						:get,
						EMPromise.resolve(11),


@@ 525,7 522,7 @@ class RegistrationTest < Minitest::Test
			iq = Blather::Stanza::Iq::Command.new
			iq.from = "test\\40example.com@cheogram.com"
			@finish = Registration::Finish.new(
				Customer.new("test", sgx: @sgx),
				customer(sgx: @sgx),
				"+15555550000"
			)
		end


@@ 569,7 566,7 @@ class RegistrationTest < Minitest::Test
				nil,
				[
					"catapult_fwd-+15555550000",
					"sip:test%40example.com@sip.cheogram.com"
					"sip:test%40example.net@sip.cheogram.com"
				]
			)
			BackendSgx::REDIS.expect(

M test/test_transaction.rb => test/test_transaction.rb +2 -2
@@ 44,7 44,7 @@ class TransactionTest < Minitest::Test
		)
		assert_raises("declined") do
			Transaction.sale(
				Customer.new("test", plan_name: "test_usd"),
				customer(plan_name: "test_usd"),
				amount: 123,
				payment_method: OpenStruct.new(token: "token")
			).sync


@@ 76,7 76,7 @@ class TransactionTest < Minitest::Test
			}]
		)
		result = Transaction.sale(
			Customer.new("test", plan_name: "test_usd"),
			customer(plan_name: "test_usd"),
			amount: 123,
			payment_method: OpenStruct.new(token: "token")
		).sync