From f6896d17e30f8ded521beecd8107b17951c6ed10 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Sun, 3 Oct 2021 21:17:39 -0500 Subject: [PATCH] Move CustomerFwd behind Customer All the previously-lazy BackendSgx data is now either all loaded or all not loaded by swapping the sgx_repo used by your CustomerRepo instance. When not loaded the fields are filled with bottom values that explode when used. When loaded the values are present in RAM and not promises at all. Most code paths do not need any of the data, a few need most of it, so this seems like a good trade-off. Most code using this object will simply never touch those fields or care about how they are loaded, etc. Of course, most of this data isn't even SGX related and should move out of here, but that would take a data model refactor/migration on the catapult_* schema. --- lib/backend_sgx.rb | 71 +++++---------- lib/bwmsgsv2_repo.rb | 63 +++++++++++++ lib/command.rb | 12 +-- lib/command_list.rb | 24 ++--- lib/customer.rb | 9 +- lib/customer_fwd.rb | 72 +++++++++++++++ lib/customer_info.rb | 11 +-- lib/customer_info_form.rb | 3 +- lib/customer_repo.rb | 24 +++-- lib/not_loaded.rb | 17 ++++ lib/registration.rb | 12 ++- lib/trivial_backend_sgx_repo.rb | 28 ++++++ sgx_jmp.rb | 31 ++++--- test/test_backend_sgx.rb | 20 +++-- test/test_command_list.rb | 26 ++---- test/test_customer_info.rb | 8 +- test/test_customer_repo.rb | 10 +-- test/test_helper.rb | 8 ++ test/test_registration.rb | 8 +- web.rb | 155 +++++++------------------------- 20 files changed, 337 insertions(+), 275 deletions(-) create mode 100644 lib/bwmsgsv2_repo.rb create mode 100644 lib/customer_fwd.rb create mode 100644 lib/not_loaded.rb create mode 100644 lib/trivial_backend_sgx_repo.rb 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