Merge branch 'conditionally-show-commands'

Stephen Paul Weber created

* 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)

Change summary

lib/command_list.rb       |  57 ++++++++++++++++++++
lib/payment_methods.rb    |   8 ++
sgx_jmp.rb                |  36 ++++++------
test/test_command_list.rb | 115 +++++++++++++++++++++++++++++++++++++++++
4 files changed, 198 insertions(+), 18 deletions(-)

Detailed changes

lib/command_list.rb 🔗

@@ -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

lib/payment_methods.rb 🔗

@@ -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

sgx_jmp.rb 🔗

@@ -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(

test/test_command_list.rb 🔗

@@ -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