~singpolyma/sgx-jmp

df67870f8b44c20267dcf1ff8d838cf8b467e6a3 — Stephen Paul Weber 1 year, 2 months ago a6e94c5 + 3e6ca1e
Merge branch 'conditionally-show-commands'

* conditionally-show-commands:
  Only show buy credit option if customer has a plan and a credit card
  Show commands conditionally (plus add voicemail record command)
4 files changed, 198 insertions(+), 18 deletions(-)

A lib/command_list.rb
M lib/payment_methods.rb
M sgx_jmp.rb
A test/test_command_list.rb
A lib/command_list.rb => lib/command_list.rb +57 -0
@@ 0,0 1,57 @@
# frozen_string_literal: true

class CommandList
	include Enumerable

	def self.for(jid)
		Customer.for_jid(jid).catch { nil }.then do |customer|
			EMPromise.resolve(customer&.registered?).catch { nil }.then do |reg|
				next Registered.for(customer, reg.phone) if reg
				CommandList.new
			end
		end
	end

	def each
		yield node: "jabber:iq:register", name: "Register"
	end

	class Registered < CommandList
		def self.for(customer, tel)
			EMPromise.all([
				REDIS.get("catapult_fwd-#{tel}"),
				customer.plan_name ? customer.payment_methods : []
			]).then do |(fwd, payment_methods)|
				klass = Class.new(Registered)
				klass.include(HasBilling) unless payment_methods.empty?
				klass.include(HasForwarding) if fwd
				klass.new
			end
		end

		def each
			super
			yield node: "number-display", name: "Display JMP Number"
			yield node: "configure-calls", name: "Configure Calls"
			yield node: "usage", name: "Show Monthly Usage"
			yield node: "reset sip account", name: "Create or Reset SIP Account"
		end
	end

	module HasForwarding
		def each
			super
			yield(
				node: "record-voicemail-greeting",
				name: "Record Voicemail Greeting"
			)
		end
	end

	module HasBilling
		def each
			super
			yield node: "buy-credit", name: "Buy account credit"
		end
	end
end

M lib/payment_methods.rb => lib/payment_methods.rb +8 -0
@@ 46,11 46,19 @@ class PaymentMethods
		}.merge(kwargs)
	end

	def empty?
		false
	end

	class Empty
		def default_payment_method; end

		def to_list_single(*)
			raise "No payment methods available"
		end

		def empty?
			true
		end
	end
end

M sgx_jmp.rb => sgx_jmp.rb +18 -18
@@ 28,6 28,7 @@ require_relative "lib/backend_sgx"
require_relative "lib/bandwidth_tn_order"
require_relative "lib/btc_sell_prices"
require_relative "lib/buy_account_credit_form"
require_relative "lib/command_list"
require_relative "lib/customer"
require_relative "lib/electrum"
require_relative "lib/em"


@@ 241,24 242,19 @@ disco_info to: Blather::JID.new(CONFIG[:component][:jid]) do |iq|
end

disco_items node: "http://jabber.org/protocol/commands" do |iq|
	sentry_hub = new_sentry_hub(iq, name: iq.node)
	reply = iq.reply
	reply.items = [
		{ node: "number-display", name: "Display JMP Number" },
		{ node: "configure-calls", name: "Configure Calls" },
		# TODO: don't show this item if no braintree methods available
		# TODO: don't show this item if no plan for this customer
		{ node: "buy-credit", name: "Buy account credit" },
		{ node: "jabber:iq:register", name: "Register" },
		{ node: "usage", name: "Show Monthly Usage" },
		{ node: "reset sip account", name: "Create or Reset SIP Account" }
	].map do |item|
		Blather::Stanza::DiscoItems::Item.new(
			iq.to,
			item[:node],
			item[:name]
		)
	end
	self << reply

	CommandList.for(iq.from.stripped).then { |list|
		reply.items = list.map do |item|
			Blather::Stanza::DiscoItems::Item.new(
				iq.to,
				item[:node],
				item[:name]
			)
		end
		self << reply
	}.catch { |e| panic(e, sentry_hub) }
end

command :execute?, node: "jabber:iq:register", sessionid: nil do |iq|


@@ 298,7 294,11 @@ def reply_with_note(iq, text, type: :info)
end

# Commands that just pass through to the SGX
command node: ["number-display", "configure-calls"] do |iq|
command node: [
	"number-display",
	"configure-calls",
	"record-voicemail-greeting"
] do |iq|
	sentry_hub = new_sentry_hub(iq, name: iq.node)
	Customer.for_jid(iq.from.stripped).then { |customer|
		sentry_hub.current_scope.set_user(

A test/test_command_list.rb => test/test_command_list.rb +115 -0
@@ 0,0 1,115 @@
# frozen_string_literal: true

require "test_helper"
require "command_list"

CommandList::Customer = Minitest::Mock.new
CommandList::REDIS = Minitest::Mock.new

class CommandListTest < Minitest::Test
	def test_for_no_customer
		CommandList::Customer.expect(
			:for_jid,
			EMPromise.reject("not found"),
			["none"]
		)
		assert_instance_of CommandList, CommandList.for("none").sync
	end
	em :test_for_no_customer

	def test_for_unregistered
		CommandList::Customer.expect(
			:for_jid,
			EMPromise.resolve(OpenStruct.new(registered?: false)),
			["unregistered"]
		)
		assert_instance_of CommandList, CommandList.for("unregistered").sync
	end
	em :test_for_unregistered

	def test_for_registered
		CommandList::REDIS.expect(
			:get,
			EMPromise.resolve(nil),
			["catapult_fwd-1"]
		)
		CommandList::Customer.expect(
			:for_jid,
			EMPromise.resolve(OpenStruct.new(
				registered?: OpenStruct.new(phone: "1"),
				payment_methods: EMPromise.resolve([])
			)),
			["registered"]
		)
		assert_equal(
			["CommandList::Registered"],
			CommandList.for("registered").sync
			.class.ancestors.map(&:name).grep(/\ACommandList::/)
		)
	end
	em :test_for_registered

	def test_for_registered_with_fwd
		CommandList::REDIS.expect(
			:get,
			EMPromise.resolve("tel:1"),
			["catapult_fwd-1"]
		)
		CommandList::Customer.expect(
			:for_jid,
			EMPromise.resolve(OpenStruct.new(
				registered?: OpenStruct.new(phone: "1"),
				payment_methods: EMPromise.resolve([])
			)),
			["registered"]
		)
		assert_kind_of(
			CommandList::HasForwarding,
			CommandList.for("registered").sync
		)
	end
	em :test_for_registered_with_fwd

	def test_for_registered_with_billing
		CommandList::REDIS.expect(
			:get,
			EMPromise.resolve(nil),
			["catapult_fwd-1"]
		)
		CommandList::Customer.expect(
			:for_jid,
			EMPromise.resolve(OpenStruct.new(
				registered?: OpenStruct.new(phone: "1"),
				plan_name: "test",
				payment_methods: EMPromise.resolve([:boop])
			)),
			["registered"]
		)
		assert_kind_of(
			CommandList::HasBilling,
			CommandList.for("registered").sync
		)
	end
	em :test_for_registered_with_billing

	def test_for_registered_with_forwarding_and_billing
		CommandList::REDIS.expect(
			:get,
			EMPromise.resolve("tel:1"),
			["catapult_fwd-1"]
		)
		CommandList::Customer.expect(
			:for_jid,
			EMPromise.resolve(OpenStruct.new(
				registered?: OpenStruct.new(phone: "1"),
				plan_name: "test",
				payment_methods: EMPromise.resolve([:boop])
			)),
			["registered"]
		)
		result = CommandList.for("registered").sync
		assert_kind_of CommandList::HasForwarding, result
		assert_kind_of CommandList::HasBilling, result
	end
	em :test_for_registered_with_forwarding_and_billing
end