diff --git a/lib/backend_sgx.rb b/lib/backend_sgx.rb index a708d5d62ed0009af55d227c772f5acc010d9437..b4fec109b3ca575ba9365a88d6289f12ca99d66e 100644 --- a/lib/backend_sgx.rb +++ b/lib/backend_sgx.rb @@ -1,71 +1,40 @@ # frozen_string_literal: true -class BackendSgx - VOICEMAIL_TRANSCRIPTION_DISABLED = 0 +require "value_semantics/monkey_patched" + +require_relative "customer_fwd" +require_relative "ibr" +require_relative "not_loaded" - def initialize(customer_id, jid=CONFIG[:sgx], creds=CONFIG[:creds]) - @customer_id = customer_id - @jid = jid - @creds = creds +class BackendSgx + value_semantics do + jid Blather::JID + creds HashOf(Symbol => String) + from_jid Blather::JID + ogm_url Either(String, nil, NotLoaded) + fwd Either(CustomerFwd, nil, NotLoaded) + transcription_enabled Either(Bool(), NotLoaded) + registered? Either(IBR, FalseClass, NotLoaded) end def register!(tel) - ibr = mkibr(:set) - ibr.nick = @creds[:account] - ibr.username = @creds[:username] - ibr.password = @creds[:password] + ibr = IBR.new(:set, @jid) + ibr.from = from_jid + ibr.nick = creds[:account] + ibr.username = creds[:username] + ibr.password = creds[:password] ibr.phone = tel IQ_MANAGER.write(ibr) end - def registered? - IQ_MANAGER.write(mkibr(:get)).catch { nil }.then do |result| - if result&.respond_to?(:registered?) && result&.registered? - result - else - false - end - end - end - def stanza(s) s.dup.tap do |stanza| - stanza.to = stanza.to.with(domain: @jid) + stanza.to = stanza.to.with(domain: jid.domain) stanza.from = from_jid.with(resource: stanza.from.resource) end end - def ogm_url - REDIS.get("catapult_ogm_url-#{from_jid}") - end - - def catapult_flag(flagbit) - REDIS.getbit( - "catapult_setting_flags-#{from_jid}", - flagbit - ).then { |x| x == 1 } - end - - def fwd_timeout - REDIS.get("catapult_fwd_timeout-#{from_jid}") - end - def set_fwd_timeout(timeout) REDIS.set("catapult_fwd_timeout-#{from_jid}", timeout) end - -protected - - def from_jid - Blather::JID.new( - "customer_#{@customer_id}", - CONFIG[:component][:jid] - ) - end - - def mkibr(type) - ibr = IBR.new(type, @jid) - ibr.from = from_jid - ibr - end end diff --git a/lib/bwmsgsv2_repo.rb b/lib/bwmsgsv2_repo.rb new file mode 100644 index 0000000000000000000000000000000000000000..312563122ea83d88ffa8274c5f319ba22dfea488 --- /dev/null +++ b/lib/bwmsgsv2_repo.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require "lazy_object" + +require_relative "customer_fwd" +require_relative "ibr" +require_relative "trivial_backend_sgx_repo" + +class Bwmsgsv2Repo + VOICEMAIL_TRANSCRIPTION_DISABLED = 0 + + def initialize(jid: CONFIG[:sgx], redis: LazyObject.new { REDIS }, **kwargs) + @jid = jid + @redis = redis + @trivial_repo = TrivialBackendSgxRepo.new(jid: jid, **kwargs) + end + + def get(customer_id) + sgx = @trivial_repo.get(customer_id) + fetch_raw(sgx.from_jid).then do |(((ogm_url, fwd_time, fwd), trans_d), reg)| + sgx.with({ + ogm_url: ogm_url, + fwd: CustomerFwd.for(fwd, fwd_time), + transcription_enabled: !trans_d, + registered?: reg + }.compact) + end + end + +protected + + def fetch_raw(from_jid) + registration(from_jid).then do |r| + EMPromise.all([from_redis(from_jid, r ? r.phone : nil), r]) + end + end + + def registration(from_jid) + ibr = IBR.new(:get, @jid) + ibr.from = from_jid + + IQ_MANAGER.write(ibr).catch { nil }.then do |result| + if result&.respond_to?(:registered?) && result&.registered? + result + else + false + end + end + end + + def from_redis(from_jid, tel) + EMPromise.all([ + @redis.mget(*[ + "catapult_ogm_url-#{from_jid}", + "catapult_fwd_timeout-#{from_jid}", + ("catapult_fwd-#{tel}" if tel) + ].compact), + @redis.getbit( + "catapult_setting_flags-#{from_jid}", VOICEMAIL_TRANSCRIPTION_DISABLED + ).then { |x| x == 1 } + ]) + end +end diff --git a/lib/command.rb b/lib/command.rb index 567cfea4e0e788d9d3284ce26bfba24d777eee9f..c5ec069ed2a38d05048b0c47886408c1ee3a32c4 100644 --- a/lib/command.rb +++ b/lib/command.rb @@ -53,7 +53,7 @@ class Command EMPromise.resolve(nil).then { Thread.current[:execution] = self sentry_hub - catch_after(yield self) + catch_after(EMPromise.resolve(yield self)) }.catch(&method(:panic)) end @@ -141,21 +141,21 @@ class Command def initialize( node, name, + customer_repo: CustomerRepo.new, list_for: ->(tel:, **) { !!tel }, - format_error: ->(e) { e.respond_to?(:message) ? e.message : e.to_s }, - &blk + format_error: ->(e) { e.respond_to?(:message) ? e.message : e.to_s } ) @node = node @name = name + @customer_repo = customer_repo @list_for = list_for @format_error = format_error - @blk = blk + @blk = ->(exe) { yield exe } end def register(blather, guards: [:execute?, node: @node, sessionid: nil]) blather.command(*guards) do |iq| - customer_repo = CustomerRepo.new - Execution.new(customer_repo, blather, @format_error, iq).execute(&@blk) + Execution.new(@customer_repo, blather, @format_error, iq).execute(&@blk) end self end diff --git a/lib/command_list.rb b/lib/command_list.rb index f0716e9b6f1fb7de41aa0f5ae58717ecdc046fa2..3307cf398b41779495c4db08539d0a16fb0b9f4a 100644 --- a/lib/command_list.rb +++ b/lib/command_list.rb @@ -9,22 +9,22 @@ class CommandList end def self.for(customer) - EMPromise.resolve(customer&.registered?).catch { nil }.then do |reg| - args_for(customer, reg).then do |kwargs| - new(@commands.select { |c| c.list_for?(**kwargs) }) - end + args_for(customer).then do |kwargs| + new(@commands.select { |c| c.list_for?(**kwargs) }) end end - def self.args_for(customer, reg) - args = { customer: customer, tel: reg ? reg.phone : nil } - return EMPromise.resolve(args) unless args[:tel] + def self.args_for(customer) + args = { + customer: customer, + tel: customer&.registered? ? customer&.registered?&.phone : nil, + fwd: customer&.fwd, + payment_methods: [] + } + return EMPromise.resolve(args) unless customer&.plan_name - EMPromise.all([ - REDIS.get("catapult_fwd-#{args[:tel]}"), - customer.plan_name ? customer.payment_methods : [] - ]).then do |(fwd, payment_methods)| - args.merge(fwd: fwd, payment_methods: payment_methods) + customer.payment_methods.then do |payment_methods| + args.merge(payment_methods: payment_methods) end end diff --git a/lib/customer.rb b/lib/customer.rb index a2916229e12f968ac7401daecb39d20fd1429138..f4dde9c402cfa0b9e1d4405c0cae685c27b266c7 100644 --- a/lib/customer.rb +++ b/lib/customer.rb @@ -14,6 +14,7 @@ require_relative "./payment_methods" require_relative "./plan" require_relative "./proxied_jid" require_relative "./sip_account" +require_relative "./trivial_backend_sgx_repo" class Customer extend Forwardable @@ -22,7 +23,7 @@ class Customer 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, :set_fwd_timeout, :catapult_flag + :set_fwd_timeout, :fwd, :transcription_enabled def_delegators :@usage, :usage_report, :message_usage, :incr_message_usage def initialize( @@ -30,7 +31,7 @@ class Customer jid, plan: CustomerPlan.new(customer_id), balance: BigDecimal(0), - sgx: BackendSgx.new(customer_id) + sgx: TrivialBackendSgxRepo.new.get(customer_id) ) @plan = plan @usage = CustomerUsage.new(customer_id) @@ -83,9 +84,7 @@ class Customer end def ogm(from_tel=nil) - @sgx.ogm_url.then do |url| - CustomerOGM.for(url, -> { fetch_vcard_temp(from_tel) }) - end + CustomerOGM.for(@sgx.ogm_url, -> { fetch_vcard_temp(from_tel) }) end def sip_account diff --git a/lib/customer_fwd.rb b/lib/customer_fwd.rb new file mode 100644 index 0000000000000000000000000000000000000000..3b309559a158e55329afa079be82b9894ac9d747 --- /dev/null +++ b/lib/customer_fwd.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +require "uri" + +class CustomerFwd + def self.for(uri, timeout) + timeout = Timeout.new(timeout) + return if !uri || timeout.zero? + URIS.fetch(uri.split(":", 2).first.to_sym) { + raise "Unknown forward URI: #{uri}" + }.new(uri, timeout) + end + + class Timeout + def initialize(s) + @timeout = s.nil? || s.to_i.negative? ? 300 : s.to_i + end + + def zero? + @timeout.zero? + end + + def to_i + @timeout + end + end + + class Tel < CustomerFwd + attr_reader :timeout + + def initialize(uri, timeout) + @tel = uri.sub(/^tel:/, "") + @timeout = timeout + end + + def to + @tel + end + end + + class SIP < CustomerFwd + attr_reader :timeout + + def initialize(uri, timeout) + @uri = uri + @timeout = timeout + end + + def to + @uri + end + end + + class XMPP < CustomerFwd + attr_reader :timeout + + def initialize(uri, timeout) + @jid = uri.sub(/^xmpp:/, "") + @timeout = timeout + end + + def to + "sip:#{ERB::Util.url_encode(@jid)}@sip.cheogram.com" + end + end + + URIS = { + tel: Tel, + sip: SIP, + xmpp: XMPP + }.freeze +end diff --git a/lib/customer_info.rb b/lib/customer_info.rb index 5d1349f581394de66e185dc1a5daf8ef8ffd6265..aded9e0cce5814c7846e4fed482f19fcd9ed17fa 100644 --- a/lib/customer_info.rb +++ b/lib/customer_info.rb @@ -15,24 +15,17 @@ class CustomerInfo end def self.for(customer, plan, expires_at) - fetch_inputs(customer, plan).then do |(auto_top_up_amount, registration)| + plan.auto_top_up_amount.then do |auto_top_up_amount| new( plan: plan, auto_top_up_amount: auto_top_up_amount, - tel: registration ? registration.phone : nil, + tel: customer.registered? ? customer.regsitered?.phone : nil, balance: customer.balance, expires_at: expires_at ) end end - def self.fetch_inputs(customer, plan) - EMPromise.all([ - plan.auto_top_up_amount, - customer.registered? - ]) - end - def account_status if plan.plan_name.nil? "Transitional" diff --git a/lib/customer_info_form.rb b/lib/customer_info_form.rb index 50238f4802874e9b44daece9a55ef580135926a9..1ab7868ff5cce282386751c1242b0c899063d3e4 100644 --- a/lib/customer_info_form.rb +++ b/lib/customer_info_form.rb @@ -1,11 +1,12 @@ # frozen_string_literal: true +require_relative "bwmsgsv2_repo" require_relative "customer_repo" require_relative "proxied_jid" require_relative "legacy_customer" class CustomerInfoForm - def initialize(customer_repo=CustomerRepo.new) + def initialize(customer_repo=CustomerRepo.new(sgx_repo: Bwmsgsv2Repo.new)) @customer_repo = customer_repo end diff --git a/lib/customer_repo.rb b/lib/customer_repo.rb index 90f35b861753af4c70af458e6f1987dbd7effa80..169006a3d3ceaa337f7f6bd8e5740ed6b11892f9 100644 --- a/lib/customer_repo.rb +++ b/lib/customer_repo.rb @@ -1,14 +1,22 @@ # frozen_string_literal: true +require "lazy_object" + require_relative "customer" require_relative "legacy_customer" require_relative "polyfill" class CustomerRepo - def initialize(redis: REDIS, db: DB, braintree: BRAINTREE) + def initialize( + redis: LazyObject.new { REDIS }, + db: LazyObject.new { DB }, + braintree: LazyObject.new { BRAINTREE }, + sgx_repo: TrivialBackendSgxRepo.new + ) @redis = redis @db = db @braintree = braintree + @sgx_repo = sgx_repo end def find(customer_id) @@ -19,7 +27,7 @@ class CustomerRepo end def find_by_jid(jid) - if jid.to_s =~ /\Acustomer_(.+)@jmp.chat\Z/ + if jid.to_s =~ /\Acustomer_(.+)@#{CONFIG[:component][:jid]}\Z/ find($1) else @redis.get("jmp_customer_id-#{jid}").then { |customer_id| @@ -46,13 +54,19 @@ 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, Blather::JID.new(jid)) + Customer.new(cid, Blather::JID.new(jid), sgx: new_sgx(cid)) end end end protected + def new_sgx(customer_id) + TrivialBackendSgxRepo.new.get(customer_id).with( + registered?: false + ) + end + def find_legacy_customer(jid) @redis.lindex("catapult_cred-#{jid}", 3).then do |tel| raise "No customer" unless tel @@ -76,9 +90,9 @@ protected FROM customer_plans LEFT JOIN balances USING (customer_id) WHERE customer_id=$1 LIMIT 1 SQL - result.then do |rows| + EMPromise.all([@sgx_repo.get(customer_id), result]).then do |(sgx, rows)| data = hydrate_plan(customer_id, rows.first&.transform_keys(&:to_sym) || {}) - Customer.new(customer_id, Blather::JID.new(jid), **data) + Customer.new(customer_id, Blather::JID.new(jid), sgx: sgx, **data) end end end diff --git a/lib/not_loaded.rb b/lib/not_loaded.rb new file mode 100644 index 0000000000000000000000000000000000000000..31c80e9f18d5f1921460e2611def476e856e7d65 --- /dev/null +++ b/lib/not_loaded.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class NotLoaded + class NotLoadedError < StandardError; end + + def initialize(name) + @name = name + end + + def respond_to_missing?(*) + true + end + + def method_missing(*) # rubocop:disable Style/MethodMissing + raise NotLoadedError, "#{@name} not loaded" + end +end diff --git a/lib/registration.rb b/lib/registration.rb index d1dd182a4a4581e5137d87008c3c53f7ae3382b8..96cace790c2543fc5a7fa7e6e169abd13ae54451 100644 --- a/lib/registration.rb +++ b/lib/registration.rb @@ -13,13 +13,11 @@ require_relative "./tel_selections" class Registration def self.for(customer, tel_selections) - customer.registered?.then do |registered| - if registered - Registered.new(registered.phone) - else - tel_selections[customer.jid].then(&:choose_tel).then do |tel| - Activation.for(customer, tel) - end + if (reg = customer.registered?) + Registered.new(reg.phone) + else + tel_selections[customer.jid].then(&:choose_tel).then do |tel| + Activation.for(customer, tel) end end end diff --git a/lib/trivial_backend_sgx_repo.rb b/lib/trivial_backend_sgx_repo.rb new file mode 100644 index 0000000000000000000000000000000000000000..dedfe4dd54f63d4ef9e196733a4f4c40e1b0f456 --- /dev/null +++ b/lib/trivial_backend_sgx_repo.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require_relative "backend_sgx" +require_relative "not_loaded" + +class TrivialBackendSgxRepo + def initialize( + jid: CONFIG[:sgx], + creds: CONFIG[:creds], + component_jid: CONFIG[:component][:jid] + ) + @jid = Blather::JID.new(jid) + @creds = creds + @component_jid = component_jid + end + + def get(customer_id) + BackendSgx.new( + jid: @jid, + creds: @creds, + from_jid: Blather::JID.new("customer_#{customer_id}", @component_jid), + ogm_url: NotLoaded.new(:ogm_url), + fwd: NotLoaded.new(:fwd_timeout), + transcription_enabled: NotLoaded.new(:transcription_enabled), + registered?: NotLoaded.new(:registered?) + ) + end +end diff --git a/sgx_jmp.rb b/sgx_jmp.rb index 29a78b5e554649a94ddd2a94389106404eb88af6..3a4f19f77450fa333f0163e6be39fba54ff5941a 100644 --- a/sgx_jmp.rb +++ b/sgx_jmp.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "pg/em/connection_pool" +require "bandwidth" require "bigdecimal" require "blather/client/dsl" # Require this first to not auto-include require "blather/client" @@ -68,6 +69,7 @@ require_relative "lib/polyfill" require_relative "lib/alt_top_up_form" require_relative "lib/add_bitcoin_address" require_relative "lib/backend_sgx" +require_relative "lib/bwmsgsv2_repo" require_relative "lib/bandwidth_tn_order" require_relative "lib/btc_sell_prices" require_relative "lib/buy_account_credit_form" @@ -100,6 +102,10 @@ BandwidthIris::Client.global_options = { username: CONFIG[:creds][:username], password: CONFIG[:creds][:password] } +BANDWIDTH_VOICE = Bandwidth::Client.new( + voice_basic_auth_user_name: CONFIG[:creds][:username], + voice_basic_auth_password: CONFIG[:creds][:password] +).voice_client.client def new_sentry_hub(stanza, name: nil) hub = Sentry.get_current_hub&.new_from_top @@ -200,7 +206,7 @@ when_ready do self << ping end - Web.run(LOG.child, CustomerRepo.new, *WEB_LISTEN) + Web.run(LOG.child, *WEB_LISTEN) end # workqueue_count MUST be 0 or else Blather uses threads! @@ -258,9 +264,7 @@ message( &.find { |el| el["jid"].to_s.start_with?("customer_") } pass unless address - CustomerRepo.new.find( - Blather::JID.new(address["jid"].to_s).node.delete_prefix("customer_") - ).then { |customer| + CustomerRepo.new.find_by_jid(address["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 @@ -362,7 +366,9 @@ disco_items node: "http://jabber.org/protocol/commands" do |iq| reply = iq.reply reply.node = "http://jabber.org/protocol/commands" - CustomerRepo.new.find_by_jid(iq.from.stripped).catch { + CustomerRepo.new(sgx_repo: Bwmsgsv2Repo.new).find_by_jid( + iq.from.stripped + ).catch { nil }.then { |customer| CommandList.for(customer) @@ -397,7 +403,8 @@ end Command.new( "jabber:iq:register", "Register", - list_for: ->(*) { true } + list_for: ->(*) { true }, + customer_repo: CustomerRepo.new(sgx_repo: Bwmsgsv2Repo.new) ) { Command.customer.catch { Sentry.add_breadcrumb(Sentry::Breadcrumb.new(message: "Customer.create")) @@ -542,7 +549,8 @@ Command.new( Command.new( "info", "Show Account Info", - list_for: ->(*) { true } + list_for: ->(*) { true }, + customer_repo: CustomerRepo.new(sgx_repo: Bwmsgsv2Repo.new) ) { Command.customer.then(&:info).then do |info| Command.finish do |reply| @@ -584,17 +592,18 @@ Command.new( Command.new( "migrate billing", "Switch from PayPal or expired trial to new billing", - list_for: ->(tel:, customer:, **) { tel && !customer&.currency } + list_for: ->(tel:, customer:, **) { tel && !customer&.currency }, + customer_repo: CustomerRepo.new(sgx_repo: Bwmsgsv2Repo.new) ) { EMPromise.all([ - Command.customer.then { |c| EMPromise.all([c, c.registered?.then(&:phone)]) }, + Command.customer, Command.reply do |reply| reply.allowed_actions = [:next] reply.command << FormTemplate.render("migrate_billing") end - ]).then do |((customer, tel), iq)| + ]).then do |(customer, iq)| Registration::Payment.for( - iq, customer, tel, + iq, customer, customer.registered?.phone, final_message: PaypalDone::MESSAGE, finish: PaypalDone ).then(&:write).catch_only(Command::Execution::FinalStanza) do |s| diff --git a/test/test_backend_sgx.rb b/test/test_backend_sgx.rb index c2434d3d51a4a93e796940cc97471d538fd598cd..60663362ae4a98154e26b0480ce961d87b0582aa 100644 --- a/test/test_backend_sgx.rb +++ b/test/test_backend_sgx.rb @@ -1,17 +1,16 @@ # frozen_string_literal: true require "test_helper" +require "bwmsgsv2_repo" require "backend_sgx" +require "trivial_backend_sgx_repo" BackendSgx::IQ_MANAGER = Minitest::Mock.new +Bwmsgsv2Repo::IQ_MANAGER = Minitest::Mock.new class BackendSgxTest < Minitest::Test - def setup - @sgx = BackendSgx.new("test") - end - def test_registered - BackendSgx::IQ_MANAGER.expect( + Bwmsgsv2Repo::IQ_MANAGER.expect( :write, EMPromise.resolve(IBR.new.tap { |ibr| ibr.registered = true }), [Matching.new do |ibr| @@ -19,12 +18,13 @@ class BackendSgxTest < Minitest::Test assert_equal "customer_test@component", ibr.from.to_s end] ) - assert @sgx.registered?.sync + sgx = Bwmsgsv2Repo.new(redis: FakeRedis.new).get("test").sync + assert sgx.registered? end em :test_registered def test_registered_not_registered - BackendSgx::IQ_MANAGER.expect( + Bwmsgsv2Repo::IQ_MANAGER.expect( :write, EMPromise.resolve(IBR.new.tap { |ibr| ibr.registered = false }), [Matching.new do |ibr| @@ -32,7 +32,8 @@ class BackendSgxTest < Minitest::Test assert_equal "customer_test@component", ibr.from.to_s end] ) - refute @sgx.registered?.sync + sgx = Bwmsgsv2Repo.new(redis: FakeRedis.new).get("test").sync + refute sgx.registered? end em :test_registered_not_registered @@ -48,7 +49,8 @@ class BackendSgxTest < Minitest::Test assert_equal "+15555550000", ibr.phone end] ) - @sgx.register!("+15555550000") + sgx = TrivialBackendSgxRepo.new.get("test") + sgx.register!("+15555550000") BackendSgx::IQ_MANAGER.verify end end diff --git a/test/test_command_list.rb b/test/test_command_list.rb index 0eca7389c1ee1699d402d8f486b3546d9a7d9b3a..0fe845106ddde6ae378b57a294d585fa3be9876f 100644 --- a/test/test_command_list.rb +++ b/test/test_command_list.rb @@ -6,6 +6,9 @@ require "command_list" CommandList::Customer = Minitest::Mock.new CommandList::REDIS = Minitest::Mock.new +CustomerRepo::REDIS = Minitest::Mock.new +CustomerRepo::DB = Minitest::Mock.new +CustomerRepo::BRAINTREE = Minitest::Mock.new class CommandListTest < Minitest::Test SETUP = begin @@ -44,11 +47,6 @@ class CommandListTest < Minitest::Test em :test_for_unregistered def test_for_registered - CommandList::REDIS.expect( - :get, - EMPromise.resolve(nil), - ["catapult_fwd-1"] - ) customer = OpenStruct.new( registered?: OpenStruct.new(phone: "1"), payment_methods: EMPromise.resolve([]) @@ -61,14 +59,10 @@ class CommandListTest < Minitest::Test em :test_for_registered def test_for_registered_with_fwd - CommandList::REDIS.expect( - :get, - EMPromise.resolve("tel:1"), - ["catapult_fwd-1"] - ) customer = OpenStruct.new( registered?: OpenStruct.new(phone: "1"), - payment_methods: EMPromise.resolve([]) + payment_methods: EMPromise.resolve([]), + fwd: OpenStruct.new ) assert_equal( ["no_customer", "registered", "fwd"], @@ -78,11 +72,6 @@ class CommandListTest < Minitest::Test em :test_for_registered_with_fwd def test_for_registered_with_credit_card - CommandList::REDIS.expect( - :get, - EMPromise.resolve(nil), - ["catapult_fwd-1"] - ) customer = OpenStruct.new( registered?: OpenStruct.new(phone: "1"), plan_name: "test", @@ -96,11 +85,6 @@ class CommandListTest < Minitest::Test em :test_for_registered_with_credit_card def test_for_registered_with_currency - CommandList::REDIS.expect( - :get, - EMPromise.resolve(nil), - ["catapult_fwd-1"] - ) customer = OpenStruct.new( registered?: OpenStruct.new(phone: "1"), currency: :USD diff --git a/test/test_customer_info.rb b/test/test_customer_info.rb index b133f65344e729e535008fddb5f6cc173246f711..3987ab1ec5e781f07a9f2ea9a1c0c359252d01ff 100644 --- a/test/test_customer_info.rb +++ b/test/test_customer_info.rb @@ -9,7 +9,7 @@ CustomerPlan::REDIS = Minitest::Mock.new class CustomerInfoTest < Minitest::Test def test_info_does_not_crash sgx = Minitest::Mock.new - sgx.expect(:registered?, EMPromise.resolve(nil)) + sgx.expect(:registered?, false) CustomerPlan::REDIS.expect( :get, @@ -25,7 +25,7 @@ class CustomerInfoTest < Minitest::Test def test_admin_info_does_not_crash sgx = Minitest::Mock.new - sgx.expect(:registered?, EMPromise.resolve(nil)) + sgx.expect(:registered?, false) CustomerPlan::REDIS.expect( :get, @@ -41,7 +41,7 @@ class CustomerInfoTest < Minitest::Test def test_inactive_info_does_not_crash sgx = Minitest::Mock.new - sgx.expect(:registered?, EMPromise.resolve(nil)) + sgx.expect(:registered?, false) CustomerPlan::REDIS.expect( :get, @@ -63,7 +63,7 @@ class CustomerInfoTest < Minitest::Test def test_inactive_admin_info_does_not_crash sgx = Minitest::Mock.new - sgx.expect(:registered?, EMPromise.resolve(nil)) + sgx.expect(:registered?, false) CustomerPlan::REDIS.expect( :get, diff --git a/test/test_customer_repo.rb b/test/test_customer_repo.rb index 80df5679dc1fda467122c9f8ca8f9e40a5c0ee81..255352dc78f557c2964a63c0ff8223cc780c5d91 100644 --- a/test/test_customer_repo.rb +++ b/test/test_customer_repo.rb @@ -8,15 +8,15 @@ class CustomerRepoTest < Minitest::Test # sgx-jmp customer "jmp_customer_jid-test" => "test@example.com", "jmp_customer_id-test@example.com" => "test", - "catapult_jid-+13334445555" => "customer_test@jmp.chat", - "catapult_cred-customer_test@jmp.chat" => [ + "catapult_jid-+13334445555" => "customer_test@component", + "catapult_cred-customer_test@component" => [ "test_bw_customer", "", "", "+13334445555" ], # sgx-jmp customer, empty DB "jmp_customer_jid-empty" => "empty@example.com", "jmp_customer_id-empty@example.com" => "empty", - "catapult_jid-+16667778888" => "customer_empty@jmp.chat", - "catapult_cred-customer_empty@jmp.chat" => [ + "catapult_jid-+16667778888" => "customer_empty@component", + "catapult_cred-customer_empty@component" => [ "test_bw_customer", "", "", "+16667778888" ], # v2 customer @@ -75,7 +75,7 @@ class CustomerRepoTest < Minitest::Test em :test_find_by_id def test_find_by_customer_jid - customer = @repo.find_by_jid("customer_test@jmp.chat").sync + customer = @repo.find_by_jid("customer_test@component").sync assert_kind_of Customer, customer assert_equal 1234, customer.balance assert_equal "merchant_usd", customer.merchant_account diff --git a/test/test_helper.rb b/test/test_helper.rb index bcd869537e4b0745516554c99222b7e2de4fc174..2da8f3a0981a2c0b212895d7cbe1061fd7149e7d 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -168,10 +168,18 @@ class FakeRedis set(key, value) end + def mget(*keys) + EMPromise.all(keys.map(&method(:get))) + end + def get(key) EMPromise.resolve(@values[key]) end + def getbit(key, bit) + get(key).then { |v| v.to_i.to_s(2)[bit].to_i } + end + def exists(*keys) EMPromise.resolve( @values.select { |k, _| keys.include? k }.size diff --git a/test/test_registration.rb b/test/test_registration.rb index 462267ad1c9cfad3f3930c00eadd640d3784022f..b176a39855317b7e319b289d7e758924e8917b97 100644 --- a/test/test_registration.rb +++ b/test/test_registration.rb @@ -20,7 +20,7 @@ end class RegistrationTest < Minitest::Test def test_for_registered sgx = OpenStruct.new( - registered?: EMPromise.resolve(OpenStruct.new(phone: "+15555550000")) + registered?: OpenStruct.new(phone: "+15555550000") ) iq = Blather::Stanza::Iq::Command.new iq.from = "test@example.com" @@ -38,7 +38,7 @@ class RegistrationTest < Minitest::Test web_manager = TelSelections.new(redis: FakeRedis.new) web_manager.set("test@example.net", "+15555550000") result = execute_command do - sgx = OpenStruct.new(registered?: EMPromise.resolve(nil)) + sgx = OpenStruct.new(registered?: false) Registration.for( customer( plan_name: "test_usd", @@ -53,7 +53,7 @@ class RegistrationTest < Minitest::Test em :test_for_activated def test_for_not_activated_with_customer_id - sgx = OpenStruct.new(registered?: EMPromise.resolve(nil)) + sgx = OpenStruct.new(registered?: false) web_manager = TelSelections.new(redis: FakeRedis.new) web_manager.set("test@example.net", "+15555550000") iq = Blather::Stanza::Iq::Command.new @@ -520,7 +520,7 @@ class RegistrationTest < Minitest::Test BackendSgx::REDIS = Minitest::Mock.new def setup - @sgx = Minitest::Mock.new(BackendSgx.new("test")) + @sgx = Minitest::Mock.new(TrivialBackendSgxRepo.new.get("test")) iq = Blather::Stanza::Iq::Command.new iq.from = "test\\40example.com@cheogram.com" @finish = Registration::Finish.new( diff --git a/web.rb b/web.rb index 3c21b5624eaa8d83676718c5205a347a81a45c94..a57efb8b459246c8a51a85adb266da6877cf9a9b 100644 --- a/web.rb +++ b/web.rb @@ -5,99 +5,12 @@ require "forwardable" require "roda" require "thin" require "sentry-ruby" -require "bandwidth" - -Faraday.default_adapter = :em_synchrony require_relative "lib/cdr" require_relative "lib/roda_capture" require_relative "lib/roda_em_promise" require_relative "lib/rack_fiber" -BANDWIDTH_VOICE = Bandwidth::Client.new( - voice_basic_auth_user_name: CONFIG[:creds][:username], - voice_basic_auth_password: CONFIG[:creds][:password] -).voice_client.client - -module CustomerFwd - def self.from_redis(redis, customer, tel) - EMPromise.all([ - redis.get("catapult_fwd-#{tel}"), - customer.fwd_timeout - ]).then do |(fwd, stimeout)| - timeout = Timeout.new(stimeout) - next if !fwd || timeout.zero? - self.for(fwd, timeout) - end - end - - def self.for(uri, timeout) - case uri - when /^tel:/ - Tel.new(uri, timeout) - when /^sip:/ - SIP.new(uri, timeout) - when /^xmpp:/ - XMPP.new(uri, timeout) - else - raise "Unknown forward URI: #{uri}" - end - end - - class Timeout - def initialize(s) - @timeout = s.nil? || s.to_i.negative? ? 300 : s.to_i - end - - def zero? - @timeout.zero? - end - - def to_i - @timeout - end - end - - class Tel - attr_reader :timeout - - def initialize(uri, timeout) - @tel = uri.sub(/^tel:/, "") - @timeout = timeout - end - - def to - @tel - end - end - - class SIP - attr_reader :timeout - - def initialize(uri, timeout) - @uri = uri - @timeout = timeout - end - - def to - @uri - end - end - - class XMPP - attr_reader :timeout - - def initialize(uri, timeout) - @jid = uri.sub(/^xmpp:/, "") - @timeout = timeout - end - - def to - "sip:#{ERB::Util.url_encode(@jid)}@sip.cheogram.com" - end - end -end - # rubocop:disable Metrics/ClassLength class Web < Roda use Rack::Fiber # Must go first! @@ -112,9 +25,8 @@ class Web < Roda attr_reader :customer_repo, :log attr_reader :true_inbound_call, :outbound_transfers - def run(log, customer_repo, *listen_on) + def run(log, *listen_on) plugin :common_logger, log, method: :info - @customer_repo = customer_repo @true_inbound_call = {} @outbound_transfers = {} Thin::Logging.logger = log @@ -127,8 +39,7 @@ class Web < Roda end extend Forwardable - def_delegators :'self.class', :customer_repo, :true_inbound_call, - :outbound_transfers + def_delegators :'self.class', :true_inbound_call, :outbound_transfers def_delegators :request, :params def log @@ -221,7 +132,7 @@ class Web < Roda end end - customer_repo.find_by_tel(params["to"]).then do |customer| + CustomerRepo.new.find_by_tel(params["to"]).then do |customer| CDR.for_inbound(customer.customer_id, params).save end }.catch(&method(:log_error)) @@ -257,7 +168,7 @@ class Web < Roda "https://jmp.chat" ) - customer_repo.find_by_tel(params["to"]).then do |customer| + CustomerRepo.new.find_by_tel(params["to"]).then do |customer| m = Blather::Stanza::Message.new m.chat_state = nil m.from = from_jid @@ -271,7 +182,7 @@ class Web < Roda end r.post "transcription" do - customer_repo.find_by_tel(params["to"]).then do |customer| + CustomerRepo.new.find_by_tel(params["to"]).then do |customer| m = Blather::Stanza::Message.new m.chat_state = nil m.from = from_jid @@ -286,19 +197,13 @@ class Web < Roda end r.post do - customer_repo + CustomerRepo + .new(sgx_repo: Bwmsgsv2Repo.new) .find_by_tel(params["to"]) - .then { |customer| - EMPromise.all([ - customer.ogm(params["from"]), - customer.catapult_flag( - BackendSgx::VOICEMAIL_TRANSCRIPTION_DISABLED - ) - ]) - }.then do |(ogm, transcription_disabled)| + .then do |customer| render :voicemail, locals: { - ogm: ogm, - transcription_enabled: !transcription_disabled + ogm: customer.ogm(params["from"]), + transcription_enabled: customer.transcription_enabled } end end @@ -316,25 +221,25 @@ class Web < Roda return render :pause, locals: { duration: 300 } end - customer_repo.find_by_tel(params["to"]).then do |customer| - CustomerFwd.from_redis(::REDIS, customer, params["to"]).then do |fwd| - if fwd - body = Bandwidth::ApiCreateCallRequest.new.tap do |cc| - cc.to = fwd.to - cc.from = params["from"] - cc.application_id = params["applicationId"] - cc.call_timeout = fwd.timeout.to_i - cc.answer_url = url inbound_calls_path(nil) - cc.disconnect_url = url inbound_calls_path(:transfer_complete) - end - true_inbound_call[pseudo_call_id] = params["callId"] - outbound_transfers[pseudo_call_id] = BANDWIDTH_VOICE.create_call( - CONFIG[:creds][:account], body: body - ).data.call_id - render :pause, locals: { duration: 300 } - else - render :redirect, locals: { to: inbound_calls_path(:voicemail) } + CustomerRepo.new( + sgx_repo: Bwmsgsv2Repo.new + ).find_by_tel(params["to"]).then(&:fwd).then do |fwd| + if fwd + body = Bandwidth::ApiCreateCallRequest.new.tap do |cc| + cc.to = fwd.to + cc.from = params["from"] + cc.application_id = params["applicationId"] + cc.call_timeout = fwd.timeout.to_i + cc.answer_url = url inbound_calls_path(nil) + cc.disconnect_url = url inbound_calls_path(:transfer_complete) end + true_inbound_call[pseudo_call_id] = params["callId"] + outbound_transfers[pseudo_call_id] = BANDWIDTH_VOICE.create_call( + CONFIG[:creds][:account], body: body + ).data.call_id + render :pause, locals: { duration: 300 } + else + render :redirect, locals: { to: inbound_calls_path(:voicemail) } end end end @@ -353,9 +258,9 @@ class Web < Roda r.post do customer_id = params["from"].sub(/^\+1/, "") - customer_repo.find(customer_id).then(:registered?).then do |reg| + CustomerRepo.new(sgx_repo: Bwmsgsv2Repo.new).find(customer_id).then do |c| render :forward, locals: { - from: reg.phone, + from: c.registered?.phone, to: params["to"] } end