~singpolyma/sgx-jmp

210ab8cb0592d25cfe84f1f9e9f68ccc54319eb0 — Stephen Paul Weber 2 years ago 2701f96
Helper to allow ordering phone number from Bandwidth v2

Uses their gem which uses Faraday. Set Faraday to em-synchrony so their gem is
now using EventMachine but still sync so their code will work unchanged.  Wrap
all uses of the gem in EM.promise_fiber to get a promise back out of that.

Implement a poll helper that can wait until a new order is complete at Bandwidth
before continuing.  They support an HTTP callback method, but only global on
account? This is much easier to work with in our context.
5 files changed, 198 insertions(+), 0 deletions(-)

M .rubocop.yml
M Gemfile
A lib/bandwidth_tn_order.rb
A test/test_bandwidth_tn_order.rb
M test/test_helper.rb
M .rubocop.yml => .rubocop.yml +3 -0
@@ 45,6 45,9 @@ Layout/SpaceAroundEqualsInParameterDefault:
Layout/AccessModifierIndentation:
  EnforcedStyle: outdent

Layout/FirstParameterIndentation:
  EnforcedStyle: consistent

Style/BlockDelimiters:
  EnforcedStyle: braces_for_chaining


M Gemfile => Gemfile +2 -0
@@ 8,9 8,11 @@ gem "dhall"
gem "em-hiredis"
gem "em-http-request"
gem "em-pg-client", git: "https://github.com/royaltm/ruby-em-pg-client"
gem "em-synchrony"
gem "em_promise.rb"
gem "eventmachine"
gem "money-open-exchange-rates"
gem "ruby-bandwidth-iris"

group(:development) do
	gem "pry-reload"

A lib/bandwidth_tn_order.rb => lib/bandwidth_tn_order.rb +87 -0
@@ 0,0 1,87 @@
# frozen_string_literal: true

require "forwardable"
require "ruby-bandwidth-iris"
Faraday.default_adapter = :em_synchrony

class BandwidthTNOrder
	def self.get(id)
		EM.promise_fiber do
			self.for(BandwidthIris::Order.get_order_response(
				# https://github.com/Bandwidth/ruby-bandwidth-iris/issues/44
				BandwidthIris::Client.new,
				id
			))
		end
	end

	def self.create(tel, name: "sgx-jmp order #{tel}")
		bw_tel = tel.sub(/^\+?1?/, "")
		EM.promise_fiber do
			Received.new(BandwidthIris::Order.create(
				name: name,
				site_id: CONFIG[:bandwidth_site],
				existing_telephone_number_order_type: {
					telephone_number_list: { telephone_number: [bw_tel] }
				}
			))
		end
	end

	def self.for(bandwidth_order)
		const_get(bandwidth_order.order_status.capitalize).new(bandwidth_order)
	rescue NameError
		new(bandwidth_order)
	end

	extend Forwardable
	def_delegators :@order, :id

	def initialize(bandwidth_order)
		@order = bandwidth_order
	end

	def status
		@order[:order_status]&.downcase&.to_sym
	end

	def error_description
		@order[:error_list]&.dig(:error, :description)
	end

	def poll
		raise "Unknown order status: #{status}"
	end

	class Received < BandwidthTNOrder
		def status
			:received
		end

		def poll
			EM.promise_timer(1).then do
				BandwidthTNOrder.get(id).then(&:poll)
			end
		end
	end

	class Complete < BandwidthTNOrder
		def status
			:complete
		end

		def poll
			EMPromise.resolve(self)
		end
	end

	class Failed < BandwidthTNOrder
		def status
			:failed
		end

		def poll
			raise "Order failed: #{id} #{error_description}"
		end
	end
end

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

require "test_helper"
require "bandwidth_tn_order"

class BandwidthTNOrderTest < Minitest::Test
	def test_for_received
		order = BandwidthTNOrder.for(BandwidthIris::Order.new(
			order_status: "RECEIVED"
		))
		assert_kind_of BandwidthTNOrder::Received, order
	end

	def test_for_complete
		order = BandwidthTNOrder.for(BandwidthIris::Order.new(
			order_status: "COMPLETE"
		))
		assert_kind_of BandwidthTNOrder::Complete, order
	end

	def test_for_failed
		order = BandwidthTNOrder.for(BandwidthIris::Order.new(
			order_status: "FAILED"
		))
		assert_kind_of BandwidthTNOrder::Failed, order
	end

	def test_for_unknown
		order = BandwidthTNOrder.for(BandwidthIris::Order.new(
			order_status: "randOmgarBagE"
		))
		assert_kind_of BandwidthTNOrder, order
		assert_equal :randomgarbage, order.status
	end

	def test_poll
		order = BandwidthTNOrder.new(BandwidthIris::Order.new)
		assert_raises { order.poll.sync }
	end
	em :test_poll

	class TestReceived < Minitest::Test
		def setup
			@order = BandwidthTNOrder::Received.new(
				BandwidthIris::Order.new(id: "oid")
			)
		end

		def test_poll
			req = stub_request(
				:get,
				"https://dashboard.bandwidth.com/v1.0/accounts//orders/oid"
			).to_return(status: 200, body: <<~RESPONSE)
				<OrderResponse>
					<OrderStatus>COMPLETE</OrderStatus>
				</OrderResponse>
			RESPONSE
			new_order = PromiseMock.new
			new_order.expect(:poll, nil)
			@order.poll.sync
			assert_requested req
		end
		em :test_poll
	end

	class TestComplete < Minitest::Test
		def setup
			@order = BandwidthTNOrder::Complete.new(BandwidthIris::Order.new)
		end

		def test_poll
			assert_equal @order, @order.poll.sync
		end
		em :test_poll
	end

	class TestFailed < Minitest::Test
		def setup
			@order = BandwidthTNOrder::Failed.new(
				BandwidthIris::Order.new(id: "oid")
			)
		end

		def test_poll
			assert_raises { @order.poll.sync }
		end
		em :test_poll
	end
end

M test/test_helper.rb => test/test_helper.rb +17 -0
@@ 70,6 70,23 @@ class Matching
	end
end

class PromiseMock < Minitest::Mock
	def then
		yield self
	end
end

module EventMachine
	class << self
		# Patch EM.add_timer to be instant in tests
		alias old_add_timer add_timer
		def add_timer(*args, &block)
			args[0] = 0
			old_add_timer(*args, &block)
		end
	end
end

module Minitest
	class Test
		def self.property(m, &block)