From 9a1a09ee69626d82266b85be09cb41f868e02e87 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Mon, 9 Aug 2021 21:37:39 -0500 Subject: [PATCH] Refactor commands to have Command and Command::Execution objects Brings the common elements of all commands together, and threads the most useful state (such as ability to reply) through automatically using the new EMPromise fiber trampoline. --- .rubocop.yml | 4 + Gemfile | 4 +- lib/bandwidth_tn_order.rb | 4 +- lib/command.rb | 146 ++++++++++ lib/command_list.rb | 73 ++--- lib/polyfill.rb | 5 + lib/registration.rb | 217 +++++++-------- lib/web_register_manager.rb | 20 +- sgx_jmp.rb | 251 +++++++---------- test/test_command_list.rb | 47 +++- test/test_helper.rb | 13 + test/test_registration.rb | 448 ++++++++++++++++-------------- test/test_web_register_manager.rb | 15 +- 13 files changed, 672 insertions(+), 575 deletions(-) create mode 100644 lib/command.rb create mode 100644 lib/polyfill.rb diff --git a/.rubocop.yml b/.rubocop.yml index cdd6878..1deee46 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -14,6 +14,10 @@ Metrics/MethodLength: Exclude: - test/* +Metrics/BlockLength: + Exclude: + - test/* + Metrics/ClassLength: Exclude: - test/* diff --git a/Gemfile b/Gemfile index 622a7e5..a1704d5 100644 --- a/Gemfile +++ b/Gemfile @@ -10,12 +10,12 @@ 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", "~> 0.0.2" +gem "em_promise.rb", "~> 0.0.3" gem "eventmachine" gem "money-open-exchange-rates" gem "ougai" gem "ruby-bandwidth-iris" -gem "sentry-ruby" +gem "sentry-ruby", "<= 4.3.1" gem "statsd-instrument", git: "https://github.com/singpolyma/statsd-instrument.git", branch: "graphite" gem "value_semantics", git: "https://github.com/singpolyma/value_semantics" diff --git a/lib/bandwidth_tn_order.rb b/lib/bandwidth_tn_order.rb index fc8b001..2753f64 100644 --- a/lib/bandwidth_tn_order.rb +++ b/lib/bandwidth_tn_order.rb @@ -8,7 +8,7 @@ require_relative "./catapult" class BandwidthTNOrder def self.get(id) - EM.promise_fiber do + EMPromise.resolve(nil).then do self.for(BandwidthIris::Order.get_order_response( # https://github.com/Bandwidth/ruby-bandwidth-iris/issues/44 BandwidthIris::Client.new, @@ -18,7 +18,7 @@ class BandwidthTNOrder end def self.create(tel, name: "sgx-jmp order #{tel}") - EM.promise_fiber do + EMPromise.resolve(nil).then do Received.new(BandwidthIris::Order.create( name: name, site_id: CONFIG[:bandwidth_site], diff --git a/lib/command.rb b/lib/command.rb new file mode 100644 index 0000000..6ec7c1e --- /dev/null +++ b/lib/command.rb @@ -0,0 +1,146 @@ +# frozen_string_literal: true + +require "sentry-ruby" +require "statsd-instrument" + +require_relative "customer_repo" + +class Command + def self.execution + Thread.current[:execution] + end + + def self.reply(stanza=nil, &blk) + execution.reply(stanza, &blk) + end + + def self.finish(*args, **kwargs, &blk) + execution.finish(*args, **kwargs, &blk) + end + + def self.customer + execution.customer + end + + def self.log + execution.log + end + + class Execution + attr_reader :customer_repo, :log, :iq + + def initialize(customer_repo, blather, format_error, iq) + @customer_repo = customer_repo + @blather = blather + @format_error = format_error + @iq = iq + @log = LOG.child(node: iq.node) + end + + def execute + StatsD.increment("command", tags: ["node:#{iq.node}"]) + EMPromise.resolve(nil).then { + Thread.current[:execution] = self + sentry_hub + catch_after(yield self) + }.catch(&method(:panic)) + end + + def reply(stanza=nil) + stanza ||= iq.reply.tap do |reply| + reply.status = :executing + end + yield stanza if block_given? + COMMAND_MANAGER.write(stanza).then do |new_iq| + @iq = new_iq + end + end + + def finish(text=nil, type: :info, status: :completed) + reply = @iq.reply + reply.status = status + yield reply if block_given? + if text + reply.note_type = type + reply.note_text = text + end + raise ErrorToSend, reply + end + + def sentry_hub + return @sentry_hub if @sentry_hub + + # Stored on Fiber-local in 4.3.1 and earlier + # https://github.com/getsentry/sentry-ruby/issues/1495 + @sentry_hub = Sentry.get_current_hub + raise "Sentry.init has not been called" unless @sentry_hub + + @sentry_hub.push_scope + @sentry_hub.current_scope.clear_breadcrumbs + @sentry_hub.current_scope.set_transaction_name(@iq.node) + @sentry_hub.current_scope.set_user(jid: @iq.from.stripped.to_s) + @sentry_hub + end + + def customer + @customer ||= @customer_repo.find_by_jid(@iq.from.stripped).then do |c| + sentry_hub.current_scope.set_user( + id: c.customer_id, + jid: @iq.from.stripped + ) + c + end + end + + protected + + def catch_after(promise) + promise.catch_only(ErrorToSend) { |e| + @blather << e.stanza + }.catch do |e| + log_error(e) + finish(@format_error.call(e), type: :error) + end + end + + def log_error(e) + @log.error( + "Error raised during #{iq.node}: #{e.class}", + e + ) + if e.is_a?(::Exception) + sentry_hub.capture_exception(e) + else + sentry_hub.capture_message(e.to_s) + end + end + end + + attr_reader :node, :name + + def initialize( + node, + name, + list_for: ->(tel:, **) { !!tel }, + format_error: ->(e) { e.respond_to?(:message) ? e.message : e.to_s }, + &blk + ) + @node = node + @name = name + @list_for = list_for + @format_error = format_error + @blk = blk + end + + def register(blather) + blather.command(:execute?, node: @node, sessionid: nil) do |iq| + customer_repo = CustomerRepo.new + Execution.new(customer_repo, blather, @format_error, iq).execute(&@blk) + end + self + end + + def list_for?(**kwargs) + @list_for.call(**kwargs) + end +end diff --git a/lib/command_list.rb b/lib/command_list.rb index d2700f3..f0716e9 100644 --- a/lib/command_list.rb +++ b/lib/command_list.rb @@ -3,65 +3,36 @@ class CommandList include Enumerable - def self.for(customer) - EMPromise.resolve(customer&.registered?).catch { nil }.then do |reg| - next Registered.for(customer, reg.phone) if reg - CommandList.new - end + def self.register(command) + @commands ||= [] + @commands << command 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)| - Registered.new(*[ - (HAS_CREDIT_CARD unless payment_methods.empty?), - (HAS_CURRENCY if customer.currency), - (HAS_FORWARDING if fwd) - ].compact) + 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 end + end - def initialize(*args) - @extra = args - end - - ALWAYS = [ - { node: "number-display", name: "Display JMP Number" }, - { node: "configure-calls", name: "Configure Calls" }, - { node: "usage", name: "Show Monthly Usage" }, - { node: "reset sip account", name: "Create or Reset SIP Account" }, - { - node: "credit cards", - name: "Credit Card Settings and Management" - } - ].freeze + def self.args_for(customer, reg) + args = { customer: customer, tel: reg ? reg.phone : nil } + return EMPromise.resolve(args) unless args[:tel] - def each - super - ([ALWAYS] + @extra).each do |commands| - commands.each { |x| yield x } - end + 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) end end - HAS_CURRENCY = [ - node: "alt top up", - name: "Buy Account Credit by Bitcoin, Mail, or Interac eTransfer" - ].freeze - - HAS_FORWARDING = [ - node: "record-voicemail-greeting", - name: "Record Voicemail Greeting" - ].freeze + def initialize(commands) + @commands = commands + end - HAS_CREDIT_CARD = [ - node: "top up", name: "Buy Account Credit by Credit Card" - ].freeze + def each(&blk) + @commands.map { |c| { node: c.node, name: c.name } }.each(&blk) + end end diff --git a/lib/polyfill.rb b/lib/polyfill.rb new file mode 100644 index 0000000..be33060 --- /dev/null +++ b/lib/polyfill.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true + +class Object + alias then yield_self +end diff --git a/lib/registration.rb b/lib/registration.rb index a9578ad..a2522ce 100644 --- a/lib/registration.rb +++ b/lib/registration.rb @@ -5,6 +5,7 @@ require "ruby-bandwidth-iris" require "securerandom" require_relative "./alt_top_up_form" +require_relative "./command" require_relative "./bandwidth_tn_order" require_relative "./em" require_relative "./error_to_send" @@ -12,54 +13,44 @@ require_relative "./oob" require_relative "./web_register_manager" class Registration - def self.for(iq, customer, web_register_manager) + def self.for(customer, web_register_manager) + jid = Command.execution.iq.from.stripped customer.registered?.then do |registered| if registered - Registered.new(iq, registered.phone) + Registered.new(registered.phone) else - web_register_manager.choose_tel(iq).then do |(riq, tel)| - Activation.for(riq, customer, tel) + web_register_manager[jid].choose_tel.then do |tel| + Activation.for(customer, tel) end end end end class Registered - def initialize(iq, tel) - @reply = iq.reply - @reply.status = :completed + def initialize(tel) @tel = tel end def write - @reply.note_type = :info - @reply.note_text = <<~NOTE - You are already registered with JMP number #{@tel} - NOTE - BLATHER << @reply - nil + Command.finish("You are already registered with JMP number #{@tel}") end end class Activation - def self.for(iq, customer, tel) + def self.for(customer, tel) if customer.active? - Finish.new(iq, customer, tel) + Finish.new(customer, tel) else - EMPromise.resolve(new(iq, customer, tel)) + EMPromise.resolve(new(customer, tel)) end end - def initialize(iq, customer, tel) - @reply = iq.reply - @reply.status = :executing - @reply.allowed_actions = [:next] - + def initialize(customer, tel) @customer = customer @tel = tel end - attr_reader :reply, :customer, :tel + attr_reader :customer, :tel FORM_FIELDS = [ { @@ -130,17 +121,16 @@ class Registration end def write - rate_center.then do |center| - form = reply.form - form.type = :form - form.title = "Activate JMP" - add_instructions(form, center) - form.fields = FORM_FIELDS - - COMMAND_MANAGER.write(reply).then { |iq| - Payment.for(iq, customer, tel) - }.then(&:write) - end + rate_center.then { |center| + Command.reply do |reply| + reply.allowed_actions = [:next] + form = reply.form + form.type = :form + form.title = "Activate JMP" + add_instructions(form, center) + form.fields = FORM_FIELDS + end + }.then { |iq| Payment.for(iq, customer, tel) }.then(&:write) end protected @@ -163,23 +153,19 @@ class Registration customer = customer.with_plan(plan_name) kinds.fetch(iq.form.field("activation_method")&.value&.to_s&.to_sym) { raise "Invalid activation method" - }.call(iq, customer, tel) + }.call(customer, tel) end class Bitcoin Payment.kinds[:bitcoin] = method(:new) - def initialize(iq, customer, tel) - @reply = iq.reply - reply.note_type = :info - reply.status = :canceled - + def initialize(customer, tel) @customer = customer @customer_id = customer.customer_id @tel = tel end - attr_reader :reply, :customer_id, :tel + attr_reader :customer_id, :tel def legacy_session_save sid = SecureRandom.hex @@ -215,9 +201,7 @@ class Registration BTC_SELL_PRICES.public_send(@customer.currency.to_s.downcase) ]).then do |(addr, _, rate)| min = CONFIG[:activation_amount] / rate - reply.note_text = note_text(min, addr) - BLATHER << reply - nil + Command.finish(note_text(min, addr), status: :canceled) end end @@ -233,31 +217,23 @@ class Registration class CreditCard Payment.kinds[:credit_card] = ->(*args) { self.for(*args) } - def self.for(iq, customer, tel) + def self.for(customer, tel) customer.payment_methods.then do |payment_methods| if (method = payment_methods.default_payment_method) - Activate.new(iq, customer, method, tel) + Activate.new(customer, method, tel) else - new(iq, customer, tel) + new(customer, tel) end end end - def initialize(iq, customer, tel) + def initialize(customer, tel) @customer = customer @tel = tel - - @reply = iq.reply - @reply.status = :executing - @reply.allowed_actions = [:next] - @reply.note_type = :info - @reply.note_text = "#{oob.desc}: #{oob.url}" end - attr_reader :reply - - def oob - oob = OOB.find_or_create(@reply.command) + def oob(reply) + oob = OOB.find_or_create(reply.command) oob.url = CONFIG[:credit_card_url].call( reply.to.stripped.to_s.gsub("\\", "%5C"), @customer.customer_id @@ -267,14 +243,17 @@ class Registration end def write - COMMAND_MANAGER.write(@reply).then do |riq| - CreditCard.for(riq, @customer, @tel).write + Command.reply { |reply| + reply.allowed_actions = [:next] + reply.note_type = :info + reply.note_text = "#{oob(reply).desc}: #{oob(reply).url}" + }.then do + CreditCard.for(@customer, @tel).then(&:write) end end class Activate - def initialize(iq, customer, payment_method, tel) - @iq = iq + def initialize(customer, payment_method, tel) @customer = customer @payment_method = payment_method @tel = tel @@ -297,7 +276,7 @@ class Registration tx.insert.then { @customer.bill_plan }.then do - Finish.new(@iq, @customer, @tel).write + Finish.new(@customer, @tel).write end end @@ -323,14 +302,13 @@ class Registration end def declined - reply = @iq.reply - reply_oob = decline_oob(reply) - reply.status = :executing - reply.allowed_actions = [:next] - reply.note_type = :error - reply.note_text = "#{reply_oob.desc}: #{reply_oob.url}" - COMMAND_MANAGER.write(reply).then do |riq| - CreditCard.for(riq, @customer, @tel).write + Command.reply { |reply| + reply_oob = decline_oob(reply) + reply.allowed_actions = [:next] + reply.note_type = :error + reply.note_text = "#{reply_oob.desc}: #{reply_oob.url}" + }.then do + CreditCard.for(@customer, @tel).then(&:write) end end end @@ -348,45 +326,49 @@ class Registration required: true }].freeze - def initialize(iq, customer, tel, error: nil) + def initialize(customer, tel, error: nil) @customer = customer @tel = tel - @reply = iq.reply - @reply.status = :executing - @reply.allowed_actions = [:next] - @form = @reply.form - @form.type = :form - @form.title = "Enter Invite Code" - @form.instructions = error - @form.fields = FIELDS + @error = error + end + + def add_form(reply) + form = reply.form + form.type = :form + form.title = "Enter Invite Code" + form.instructions = @error if @error + form.fields = FIELDS end def write - COMMAND_MANAGER.write(@reply).then do |iq| - guard_too_many_tries.then { - verify(iq.form.field("code")&.value&.to_s) - }.then { - Finish.new(iq, @customer, @tel) - }.catch_only(Invalid) { |e| - invalid_code(iq, e) - }.then(&:write) - end + Command.reply { |reply| + reply.allowed_actions = [:next] + add_form(reply) + }.then(&method(:parse)) + end + + def parse(iq) + guard_too_many_tries.then { + verify(iq.form.field("code")&.value&.to_s) + }.then { + Finish.new(@customer, @tel) + }.catch_only(Invalid, &method(:invalid_code)).then(&:write) end protected def guard_too_many_tries - REDIS.get("jmp_invite_tries-#{@customer.customer_id}").then do |t| + REDIS.get("jmp_invite_tries-#{customer_id}").then do |t| raise Invalid, "Too many wrong attempts" if t.to_i > 10 end end - def invalid_code(iq, e) + def invalid_code(e) EMPromise.all([ - REDIS.incr("jmp_invite_tries-#{@customer.customer_id}").then do - REDIS.expire("jmp_invite_tries-#{@customer.customer_id}", 60 * 60) + REDIS.incr("jmp_invite_tries-#{customer_id}").then do + REDIS.expire("jmp_invite_tries-#{customer_id}", 60 * 60) end, - InviteCode.new(iq, @customer, @tel, error: e.message) + InviteCode.new(@customer, @tel, error: e.message) ]).then(&:last) end @@ -395,7 +377,7 @@ class Registration end def verify(code) - EM.promise_fiber do + EMPromise.resolve(nil).then do DB.transaction do valid = DB.exec(<<~SQL, [customer_id, code]).cmd_tuples.positive? UPDATE invites SET used_by_id=$1, used_at=LOCALTIMESTAMP @@ -411,10 +393,7 @@ class Registration class Mail Payment.kinds[:mail] = method(:new) - def initialize(iq, _customer, _tel) - @reply = iq.reply - @reply.status = :canceled - end + def initialize(_customer, _tel); end def form form = Blather::Stanza::X.new(:result) @@ -437,18 +416,15 @@ class Registration end def write - @reply.command << form - BLATHER << @reply + Command.finish(status: :canceled) do |reply| + reply.command << form + end end end end class Finish - def initialize(iq, customer, tel) - @reply = iq.reply - @reply.status = :completed - @reply.note_type = :info - @reply.note_text = "Your JMP account has been activated as #{tel}" + def initialize(customer, tel) @customer = customer @tel = tel end @@ -457,11 +433,11 @@ class Registration BandwidthTNOrder.create(@tel).then(&:poll).then( ->(_) { customer_active_tel_purchased }, lambda do |_| - @reply.note_type = :error - @reply.note_text = + Command.finish( "The JMP number #{@tel} is no longer available, " \ - "please visit https://jmp.chat and choose another." - BLATHER << @reply + "please visit https://jmp.chat and choose another.", + type: :error + ) end ) end @@ -469,27 +445,28 @@ class Registration protected def cheogram_sip_addr - "sip:#{ERB::Util.url_encode(@reply.to.stripped.to_s)}@sip.cheogram.com" + jid = Command.execution.iq.from.stripped + "sip:#{ERB::Util.url_encode(jid)}@sip.cheogram.com" end - def raise_setup_error - @reply.note_type = :error - @reply.note_text = + def raise_setup_error(e) + Command.log.error "@customer.register! failed", e + Command.finish( "There was an error setting up your number, " \ - "please contact JMP support." - raise ErrorToSend, @reply + "please contact JMP support.", + type: :error + ) end def customer_active_tel_purchased - @customer.register!(@tel).catch { |e| - LOG.error "@customer.register! failed", e - raise_setup_error - }.then { + @customer.register!(@tel).catch(&method(:raise_setup_error)).then { EMPromise.all([ REDIS.set("catapult_fwd-#{@tel}", cheogram_sip_addr), @customer.fwd_timeout = 25 # ~5 seconds / ring, 5 rings ]) - }.then { BLATHER << @reply } + }.then do + Command.finish("Your JMP account has been activated as #{@tel}") + end end end end diff --git a/lib/web_register_manager.rb b/lib/web_register_manager.rb index 167ad53..7957355 100644 --- a/lib/web_register_manager.rb +++ b/lib/web_register_manager.rb @@ -15,29 +15,23 @@ class WebRegisterManager @tel_map[jid.to_s] end - def choose_tel(iq) - self[iq&.from&.stripped].choose_tel(iq) - end - class HaveTel def initialize(tel) @tel = tel end - def choose_tel(iq) - EMPromise.resolve([iq, @tel]) + def choose_tel + EMPromise.resolve(@tel) end end class ChooseTel - def choose_tel(iq) - reply = iq.reply - reply.status = :completed - reply.note_type = :error - reply.note_text = + def choose_tel + Command.finish( "You have not chosen a phone number yet, please return to " \ - "https://jmp.chat and choose one now." - raise ErrorToSend, reply + "https://jmp.chat and choose one now.", + type: :error + ) end end end diff --git a/sgx_jmp.rb b/sgx_jmp.rb index 2613ca4..c2f8db1 100644 --- a/sgx_jmp.rb +++ b/sgx_jmp.rb @@ -36,12 +36,14 @@ singleton_class.class_eval do Blather::DSL.append_features(self) end +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/bandwidth_tn_order" require_relative "lib/btc_sell_prices" require_relative "lib/buy_account_credit_form" +require_relative "lib/command" require_relative "lib/command_list" require_relative "lib/customer" require_relative "lib/customer_repo" @@ -113,7 +115,10 @@ end BRAINTREE = AsyncBraintree.new(**CONFIG[:braintree]) def panic(e, hub=nil) - LOG.fatal "Error raised during event loop: #{e.class}", e + (Thread.current[:log] || LOG).fatal( + "Error raised during event loop: #{e.class}", + e + ) if e.is_a?(::Exception) (hub || Sentry).capture_exception(e, hint: { background: false }) else @@ -370,174 +375,120 @@ iq "/iq/ns:services", ns: "urn:xmpp:extdisco:2" do |iq| self << reply end -command :execute?, node: "jabber:iq:register", sessionid: nil do |iq| - StatsD.increment("command", tags: ["node:#{iq.node}"]) - - sentry_hub = new_sentry_hub(iq, name: iq.node) - EMPromise.resolve(nil).then { - CustomerRepo.new.find_by_jid(iq.from.stripped) - }.catch { - sentry_hub.add_breadcrumb(Sentry::Breadcrumb.new( - message: "Customer.create" - )) - CustomerRepo.new.create(iq.from.stripped) +Command.new( + "jabber:iq:register", + "Register", + list_for: ->(*) { true } +) { + Command.customer.catch { + Sentry.add_breadcrumb(Sentry::Breadcrumb.new(message: "Customer.create")) + Command.execution.customer_repo.create(Command.execution.iq.from.stripped) }.then { |customer| - sentry_hub.current_scope.set_user( - id: customer.customer_id, - jid: iq.from.stripped.to_s - ) - sentry_hub.add_breadcrumb(Sentry::Breadcrumb.new( - message: "Registration.for" - )) - Registration.for( - iq, - customer, - web_register_manager - ).then(&:write).then { StatsD.increment("registration.completed") } - }.catch_only(ErrorToSend) { |e| - self << e.stanza - }.catch { |e| panic(e, sentry_hub) } -end - -def reply_with_note(iq, text, type: :info) - reply = iq.reply - reply.status = :completed - reply.note_type = type - reply.note_text = text - - self << reply -end + Sentry.add_breadcrumb(Sentry::Breadcrumb.new(message: "Registration.for")) + Registration.for(customer, web_register_manager).then(&:write) + }.then { StatsD.increment("registration.completed") } +}.register(self).then(&CommandList.method(:register)) # Commands that just pass through to the SGX -command node: [ - "number-display", - "configure-calls", - "record-voicemail-greeting" -] do |iq| - StatsD.increment("command", tags: ["node:#{iq.node}"]) - - sentry_hub = new_sentry_hub(iq, name: iq.node) - CustomerRepo.new.find_by_jid(iq.from.stripped).then { |customer| - sentry_hub.current_scope.set_user( - id: customer.customer_id, - jid: iq.from.stripped.to_s - ) - - customer.stanza_from(iq) - }.catch { |e| panic(e, sentry_hub) } +{ + "number-display" => ["Display JMP Number"], + "configure-calls" => ["Configure Calls"], + "record-voicemail-greeting" => [ + "Record Voicemail Greeting", + list_for: ->(fwd: nil, **) { !!fwd } + ] +}.each do |node, args| + Command.new(node, *args) { + Command.customer.then do |customer| + customer.stanza_from(Command.execution.iq) + end + }.register(self).then(&CommandList.method(:register)) end -command :execute?, node: "credit cards", sessionid: nil do |iq| - StatsD.increment("command", tags: ["node:#{iq.node}"]) - - sentry_hub = new_sentry_hub(iq, name: iq.node) - reply = iq.reply - reply.status = :completed - - CustomerRepo.new.find_by_jid(iq.from.stripped).then { |customer| - oob = OOB.find_or_create(reply.command) - oob.url = CONFIG[:credit_card_url].call( +Command.new( + "credit cards", + "Credit Card Settings and Management" +) { + Command.customer.then do |customer| + url = CONFIG[:credit_card_url].call( reply.to.stripped.to_s.gsub("\\", "%5C"), customer.customer_id ) - oob.desc = "Manage credits cards and settings" - - reply.note_type = :info - reply.note_text = "#{oob.desc}: #{oob.url}" - - self << reply - }.catch { |e| panic(e, sentry_hub) } -end - -command :execute?, node: "top up", sessionid: nil do |iq| - StatsD.increment("command", tags: ["node:#{iq.node}"]) - - sentry_hub = new_sentry_hub(iq, name: iq.node) - reply = iq.reply - reply.allowed_actions = [:complete] - - CustomerRepo.new.find_by_jid(iq.from.stripped).then { |customer| + desc = "Manage credits cards and settings" + Command.finish("#{desc}: #{url}") do |reply| + oob = OOB.find_or_create(reply.command) + oob.url = url + oob.desc = desc + end + end +}.register(self).then(&CommandList.method(:register)) + +Command.new( + "top up", + "Buy Account Credit by Credit Card", + list_for: ->(payment_methods: [], **) { !payment_methods.empty? }, + format_error: ->(e) { "Failed to buy credit, system said: #{e.message}" } +) { + Command.customer.then { |customer| BuyAccountCreditForm.for(customer).then do |credit_form| - credit_form.add_to_form(reply.form) - COMMAND_MANAGER.write(reply).then { |iq2| [customer, credit_form, iq2] } + Command.reply { |reply| + reply.allowed_actions = [:complete] + credit_form.add_to_form(reply.form) + }.then do |iq| + Transaction.sale(customer, **credit_form.parse(iq.form)) + end end - }.then { |(customer, credit_form, iq2)| - iq = iq2 # This allows the catch to use it also - Transaction.sale(customer, **credit_form.parse(iq2.form)) }.then { |transaction| transaction.insert.then do - reply_with_note(iq, "#{transaction} added to your account balance.") + Command.finish("#{transaction} added to your account balance.") end - }.catch_only(BuyAccountCreditForm::AmountValidationError) { |e| - reply_with_note(iq, e.message, type: :error) - }.catch { |e| - sentry_hub.capture_exception(e) - text = "Failed to buy credit, system said: #{e.message}" - reply_with_note(iq, text, type: :error) - }.catch { |e| panic(e, sentry_hub) } -end - -command :execute?, node: "alt top up", sessionid: nil do |iq| - StatsD.increment("command", tags: ["node:#{iq.node}"]) - - sentry_hub = new_sentry_hub(iq, name: iq.node) - reply = iq.reply - reply.status = :executing - reply.allowed_actions = [:complete] - - CustomerRepo.new.find_by_jid(iq.from.stripped).then { |customer| - sentry_hub.current_scope.set_user( - id: customer.customer_id, - jid: iq.from.stripped.to_s - ) - + }.catch_only(BuyAccountCreditForm::AmountValidationError) do |e| + Command.finish(e.message, type: :error) + end +}.register(self).then(&CommandList.method(:register)) + +Command.new( + "alt top up", + "Buy Account Credit by Bitcoin, Mail, or Interac eTransfer", + list_for: ->(customer:, **) { !!customer.currency } +) { + Command.customer.then { |customer| EMPromise.all([AltTopUpForm.for(customer), customer]) - }.then { |(alt_form, customer)| - reply.command << alt_form.form - - COMMAND_MANAGER.write(reply).then do |iq2| - AddBitcoinAddress.for(iq2, alt_form, customer).write + }.then do |(alt_form, customer)| + Command.reply { |reply| + reply.allowed_actions = [:complete] + reply.command << alt_form.form + }.then do |iq| + AddBitcoinAddress.for(iq, alt_form, customer).write end - }.catch { |e| panic(e, sentry_hub) } -end - -command :execute?, node: "reset sip account", sessionid: nil do |iq| - StatsD.increment("command", tags: ["node:#{iq.node}"]) - - sentry_hub = new_sentry_hub(iq, name: iq.node) - CustomerRepo.new.find_by_jid(iq.from.stripped).then { |customer| - sentry_hub.current_scope.set_user( - id: customer.customer_id, - jid: iq.from.stripped.to_s - ) - customer.reset_sip_account - }.then { |sip_account| - reply = iq.reply - reply.command << sip_account.form - BLATHER << reply - }.catch { |e| panic(e, sentry_hub) } -end - -command :execute?, node: "usage", sessionid: nil do |iq| - StatsD.increment("command", tags: ["node:#{iq.node}"]) + end +}.register(self).then(&CommandList.method(:register)) + +Command.new( + "reset sip account", + "Create or Reset SIP Account" +) { + Command.customer.then(&:reset_sip_account).then do |sip_account| + Command.finish do |reply| + reply.command << sip_account.form + end + end +}.register(self).then(&CommandList.method(:register)) - sentry_hub = new_sentry_hub(iq, name: iq.node) +Command.new( + "usage", + "Show Monthly Usage" +) { report_for = (Date.today..(Date.today << 1)) - CustomerRepo.new.find_by_jid(iq.from.stripped).then { |customer| - sentry_hub.current_scope.set_user( - id: customer.customer_id, - jid: iq.from.stripped.to_s - ) - + Command.customer.then { |customer| customer.usage_report(report_for) - }.then { |usage_report| - reply = iq.reply - reply.status = :completed - reply.command << usage_report.form - BLATHER << reply - }.catch { |e| panic(e, sentry_hub) } -end + }.then do |usage_report| + Command.finish do |reply| + reply.command << usage_report.form + end + end +}.register(self).then(&CommandList.method(:register)) command :execute?, node: "web-register", sessionid: nil do |iq| StatsD.increment("command", tags: ["node:#{iq.node}"]) diff --git a/test/test_command_list.rb b/test/test_command_list.rb index d8939f8..0eca738 100644 --- a/test/test_command_list.rb +++ b/test/test_command_list.rb @@ -1,20 +1,45 @@ # frozen_string_literal: true require "test_helper" +require "command" require "command_list" CommandList::Customer = Minitest::Mock.new CommandList::REDIS = Minitest::Mock.new class CommandListTest < Minitest::Test + SETUP = begin + [ + Command.new("no_customer", "", list_for: ->(**) { true }), + Command.new("registered", "", list_for: ->(tel:, **) { !!tel }), + Command.new("fwd", "", list_for: ->(fwd: nil, **) { !!fwd }), + Command.new( + "currency", "", + list_for: ->(customer: nil, **) { !!customer&.currency } + ), + Command.new( + "cc", "", + list_for: ->(payment_methods: [], **) { !payment_methods.empty? } + ) + ].each do |c| + CommandList.register(c) + end + end + def test_for_no_customer - assert_instance_of CommandList, CommandList.for(nil).sync + assert_equal( + ["no_customer"], + CommandList.for(nil).sync.map { |c| c[:node] } + ) end em :test_for_no_customer def test_for_unregistered customer = OpenStruct.new(registered?: false) - assert_instance_of CommandList, CommandList.for(customer).sync + assert_equal( + ["no_customer"], + CommandList.for(customer).sync.map { |c| c[:node] } + ) end em :test_for_unregistered @@ -29,9 +54,8 @@ class CommandListTest < Minitest::Test payment_methods: EMPromise.resolve([]) ) assert_equal( - ["CommandList::Registered"], - CommandList.for(customer).sync - .class.ancestors.map(&:name).grep(/\ACommandList::/) + ["no_customer", "registered"], + CommandList.for(customer).sync.map { |c| c[:node] } ) end em :test_for_registered @@ -47,8 +71,8 @@ class CommandListTest < Minitest::Test payment_methods: EMPromise.resolve([]) ) assert_equal( - CommandList::HAS_FORWARDING, - CommandList::HAS_FORWARDING & CommandList.for(customer).sync.to_a + ["no_customer", "registered", "fwd"], + CommandList.for(customer).sync.map { |c| c[:node] } ) end em :test_for_registered_with_fwd @@ -65,8 +89,8 @@ class CommandListTest < Minitest::Test payment_methods: EMPromise.resolve([:boop]) ) assert_equal( - CommandList::HAS_CREDIT_CARD, - CommandList::HAS_CREDIT_CARD & CommandList.for(customer).sync.to_a + ["no_customer", "registered", "cc"], + CommandList.for(customer).sync.map { |c| c[:node] } ) end em :test_for_registered_with_credit_card @@ -81,10 +105,9 @@ class CommandListTest < Minitest::Test registered?: OpenStruct.new(phone: "1"), currency: :USD ) - assert_equal( - CommandList::HAS_CURRENCY, - CommandList::HAS_CURRENCY & CommandList.for(customer).sync.to_a + ["no_customer", "registered", "currency"], + CommandList.for(customer).sync.map { |c| c[:node] } ) end em :test_for_registered_with_currency diff --git a/test/test_helper.rb b/test/test_helper.rb index e70b9fe..5c953f4 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -10,6 +10,7 @@ require "em_promise" require "fiber" require "minitest/autorun" require "rantly/minitest_extensions" +require "sentry-ruby" require "webmock/minitest" begin require "pry-rescue/minitest" @@ -34,6 +35,8 @@ end require "backend_sgx" +Sentry.init + CONFIG = { sgx: "sgx", component: { @@ -78,6 +81,16 @@ CONFIG = { electrum_notify_url: ->(*) { "http://notify.example.com" } }.freeze +def panic(e) + raise e +end + +LOG = Class.new { + def child(*) + Minitest::Mock.new + end +}.new.freeze + BLATHER = Class.new { def <<(*); end }.new.freeze diff --git a/test/test_registration.rb b/test/test_registration.rb index b20920e..8743692 100644 --- a/test/test_registration.rb +++ b/test/test_registration.rb @@ -4,6 +4,19 @@ require "test_helper" require "customer" require "registration" +def execute_command( + iq=Blather::Stanza::Iq::Command.new.tap { |i| i.from = "test@example.com" }, + blather: BLATHER, + &blk +) + Command::Execution.new( + Minitest::Mock.new, + blather, + :to_s.to_proc, + iq + ).execute(&blk).sync +end + class RegistrationTest < Minitest::Test def test_for_registered sgx = OpenStruct.new( @@ -11,11 +24,12 @@ class RegistrationTest < Minitest::Test ) iq = Blather::Stanza::Iq::Command.new iq.from = "test@example.com" - result = Registration.for( - iq, - Customer.new("test", sgx: sgx), - Minitest::Mock.new - ).sync + result = execute_command(iq) do + Registration.for( + Customer.new("test", sgx: sgx), + Minitest::Mock.new + ) + end assert_kind_of Registration::Registered, result end em :test_for_registered @@ -26,16 +40,17 @@ class RegistrationTest < Minitest::Test web_manager["test@example.com"] = "+15555550000" iq = Blather::Stanza::Iq::Command.new iq.from = "test@example.com" - result = Registration.for( - iq, - Customer.new( - "test", - plan_name: "test_usd", - expires_at: Time.now + 999, - sgx: sgx - ), - web_manager - ).sync + result = execute_command(iq) do + Registration.for( + Customer.new( + "test", + plan_name: "test_usd", + expires_at: Time.now + 999, + sgx: sgx + ), + web_manager + ) + end assert_kind_of Registration::Finish, result end em :test_for_activated @@ -46,20 +61,20 @@ class RegistrationTest < Minitest::Test web_manager["test@example.com"] = "+15555550000" iq = Blather::Stanza::Iq::Command.new iq.from = "test@example.com" - result = Registration.for( - iq, - Customer.new("test", sgx: sgx), - web_manager - ).sync + result = execute_command(iq) do + Registration.for( + Customer.new("test", sgx: sgx), + web_manager + ) + end assert_kind_of Registration::Activation, result end em :test_for_not_activated_with_customer_id class ActivationTest < Minitest::Test - Registration::Activation::COMMAND_MANAGER = Minitest::Mock.new + Command::COMMAND_MANAGER = Minitest::Mock.new def setup - iq = Blather::Stanza::Iq::Command.new - @activation = Registration::Activation.new(iq, "test", "+15555550000") + @activation = Registration::Activation.new("test", "+15555550000") end def test_write @@ -82,12 +97,9 @@ class RegistrationTest < Minitest::Test RESPONSE - result = Minitest::Mock.new - result.expect(:then, result) - result.expect(:then, EMPromise.resolve(:test_result)) - Registration::Activation::COMMAND_MANAGER.expect( + Command::COMMAND_MANAGER.expect( :write, - result, + EMPromise.reject(:test_result), [Matching.new do |iq| assert_equal :form, iq.form.type assert_equal( @@ -96,7 +108,11 @@ class RegistrationTest < Minitest::Test ) end] ) - assert_equal :test_result, @activation.write.sync + assert_equal( + :test_result, + execute_command { @activation.write.catch { |e| e } } + ) + assert_mock Command::COMMAND_MANAGER end em :test_write end @@ -161,7 +177,6 @@ class RegistrationTest < Minitest::Test class BitcoinTest < Minitest::Test Registration::Payment::Bitcoin::BTC_SELL_PRICES = Minitest::Mock.new - Registration::Payment::Bitcoin::BLATHER = Minitest::Mock.new Customer::REDIS = Minitest::Mock.new def setup @@ -172,9 +187,7 @@ class RegistrationTest < Minitest::Test :add_btc_address, EMPromise.resolve("testaddr") ) - iq = Blather::Stanza::Iq::Command.new @bitcoin = Registration::Payment::Bitcoin.new( - iq, @customer, "+15555550000" ) @@ -192,7 +205,8 @@ class RegistrationTest < Minitest::Test You will receive a notification when your payment is complete. NOTE - Registration::Payment::Bitcoin::BLATHER.expect( + blather = Minitest::Mock.new + blather.expect( :<<, nil, [Matching.new do |reply| @@ -207,19 +221,18 @@ class RegistrationTest < Minitest::Test EMPromise.resolve(BigDecimal.new(1)) ) @bitcoin.stub(:save, EMPromise.resolve(nil)) do - @bitcoin.write.sync + execute_command(blather: blather) do + @bitcoin.write + end end - Registration::Payment::Bitcoin::BLATHER.verify + assert_mock blather end em :test_write end class CreditCardTest < Minitest::Test def setup - @iq = Blather::Stanza::Iq::Command.new - @iq.from = "test@example.com" @credit_card = Registration::Payment::CreditCard.new( - @iq, Customer.new("test"), "+15555550000" ) @@ -234,7 +247,6 @@ class RegistrationTest < Minitest::Test assert_kind_of( Registration::Payment::CreditCard::Activate, Registration::Payment::CreditCard.for( - @iq, customer, "+15555550000" ).sync @@ -242,14 +254,27 @@ class RegistrationTest < Minitest::Test end em :test_for - def test_reply - assert_equal [:execute, :next], @credit_card.reply.allowed_actions - assert_equal( - "Add credit card, then return here to continue: " \ - "http://creditcard.example.com", - @credit_card.reply.note.content - ) + def test_write + result = execute_command do + Command::COMMAND_MANAGER.expect( + :write, + EMPromise.reject(:test_result), + [Matching.new do |reply| + assert_equal [:execute, :next], reply.allowed_actions + assert_equal( + "Add credit card, then return here to continue: " \ + "http://creditcard.example.com", + reply.note.content + ) + end] + ) + + @credit_card.write.catch { |e| e } + end + + assert_equal :test_result, result end + em :test_write end class ActivateTest < Minitest::Test @@ -257,8 +282,7 @@ class RegistrationTest < Minitest::Test Minitest::Mock.new Registration::Payment::CreditCard::Activate::Transaction = Minitest::Mock.new - Registration::Payment::CreditCard::Activate::COMMAND_MANAGER = - Minitest::Mock.new + Command::COMMAND_MANAGER = Minitest::Mock.new def test_write transaction = PromiseMock.new @@ -277,19 +301,19 @@ class RegistrationTest < Minitest::Test assert_equal CONFIG[:activation_amount], amount assert_equal :test_default_method, payment_method end - iq = Blather::Stanza::Iq::Command.new customer.expect(:bill_plan, nil) Registration::Payment::CreditCard::Activate::Finish.expect( :new, OpenStruct.new(write: nil), - [Blather::Stanza::Iq, customer, "+15555550000"] + [customer, "+15555550000"] ) - Registration::Payment::CreditCard::Activate.new( - iq, - customer, - :test_default_method, - "+15555550000" - ).write.sync + execute_command do + Registration::Payment::CreditCard::Activate.new( + customer, + :test_default_method, + "+15555550000" + ).write + end Registration::Payment::CreditCard::Activate::Transaction.verify transaction.verify customer.verify @@ -301,21 +325,11 @@ class RegistrationTest < Minitest::Test customer = Minitest::Mock.new( Customer.new("test", plan_name: "test_usd") ) - Registration::Payment::CreditCard::Activate::Transaction.expect( - :sale, - EMPromise.reject("declined") - ) do |acustomer, amount:, payment_method:| - assert_operator customer, :===, acustomer - assert_equal CONFIG[:activation_amount], amount - assert_equal :test_default_method, payment_method - end iq = Blather::Stanza::Iq::Command.new iq.from = "test@example.com" - result = Minitest::Mock.new - result.expect(:then, nil) - Registration::Payment::CreditCard::Activate::COMMAND_MANAGER.expect( + Command::COMMAND_MANAGER.expect( :write, - result, + EMPromise.reject(:test_result), [Matching.new do |reply| assert_equal :error, reply.note_type assert_equal( @@ -325,12 +339,23 @@ class RegistrationTest < Minitest::Test ) end] ) - Registration::Payment::CreditCard::Activate.new( - iq, - customer, - :test_default_method, - "+15555550000" - ).write.sync + result = execute_command do + Registration::Payment::CreditCard::Activate::Transaction.expect( + :sale, + EMPromise.reject("declined") + ) do |acustomer, amount:, payment_method:| + assert_operator customer, :===, acustomer + assert_equal CONFIG[:activation_amount], amount + assert_equal :test_default_method, payment_method + end + + Registration::Payment::CreditCard::Activate.new( + customer, + :test_default_method, + "+15555550000" + ).write.catch { |e| e } + end + assert_equal :test_result, result Registration::Payment::CreditCard::Activate::Transaction.verify end em :test_write_declines @@ -341,164 +366,157 @@ class RegistrationTest < Minitest::Test Minitest::Mock.new Registration::Payment::InviteCode::REDIS = Minitest::Mock.new - Registration::Payment::InviteCode::COMMAND_MANAGER = - Minitest::Mock.new + Command::COMMAND_MANAGER = Minitest::Mock.new Registration::Payment::InviteCode::Finish = Minitest::Mock.new - def test_write customer = Customer.new("test", plan_name: "test_usd") - Registration::Payment::InviteCode::REDIS.expect( - :get, - EMPromise.resolve(nil), - ["jmp_invite_tries-test"] - ) - Registration::Payment::InviteCode::COMMAND_MANAGER.expect( - :write, - EMPromise.resolve( - Blather::Stanza::Iq::Command.new.tap { |iq| - iq.form.fields = [{ var: "code", value: "abc" }] - } - ), - [Matching.new do |reply| - assert_equal :form, reply.form.type - assert_nil reply.form.instructions - end] - ) Registration::Payment::InviteCode::DB.expect(:transaction, true, []) Registration::Payment::InviteCode::Finish.expect( :new, OpenStruct.new(write: nil), [ - Blather::Stanza::Iq::Command, customer, "+15555550000" ] ) - iq = Blather::Stanza::Iq::Command.new - iq.from = "test@example.com" - Registration::Payment::InviteCode.new( - iq, - customer, - "+15555550000" - ).write.sync - Registration::Payment::InviteCode::COMMAND_MANAGER.verify - Registration::Payment::InviteCode::DB.verify - Registration::Payment::InviteCode::REDIS.verify - Registration::Payment::InviteCode::Finish.verify + execute_command do + Registration::Payment::InviteCode::REDIS.expect( + :get, + EMPromise.resolve(nil), + ["jmp_invite_tries-test"] + ) + Command::COMMAND_MANAGER.expect( + :write, + EMPromise.resolve( + Blather::Stanza::Iq::Command.new.tap { |iq| + iq.form.fields = [{ var: "code", value: "abc" }] + } + ), + [Matching.new do |reply| + assert_equal :form, reply.form.type + assert_nil reply.form.instructions + end] + ) + + Registration::Payment::InviteCode.new( + customer, + "+15555550000" + ).write + end + assert_mock Command::COMMAND_MANAGER + assert_mock Registration::Payment::InviteCode::DB + assert_mock Registration::Payment::InviteCode::REDIS + assert_mock Registration::Payment::InviteCode::Finish end em :test_write def test_write_bad_code - customer = Customer.new("test", plan_name: "test_usd") - Registration::Payment::InviteCode::REDIS.expect( - :get, - EMPromise.resolve(0), - ["jmp_invite_tries-test"] - ) - Registration::Payment::InviteCode::COMMAND_MANAGER.expect( - :write, - EMPromise.resolve( - Blather::Stanza::Iq::Command.new.tap { |iq| - iq.form.fields = [{ var: "code", value: "abc" }] - } - ), - [Matching.new do |reply| - assert_equal :form, reply.form.type - assert_nil reply.form.instructions - end] - ) - Registration::Payment::InviteCode::DB.expect(:transaction, []) do - raise Registration::Payment::InviteCode::Invalid, "wut" - end - Registration::Payment::InviteCode::REDIS.expect( - :incr, - EMPromise.resolve(nil), - ["jmp_invite_tries-test"] - ) - Registration::Payment::InviteCode::REDIS.expect( - :expire, - EMPromise.resolve(nil), - ["jmp_invite_tries-test", 60 * 60] - ) - Registration::Payment::InviteCode::COMMAND_MANAGER.expect( - :write, - EMPromise.reject(Promise::Error.new), - [Matching.new do |reply| - assert_equal :form, reply.form.type - assert_equal "wut", reply.form.instructions - end] - ) - iq = Blather::Stanza::Iq::Command.new - iq.from = "test@example.com" - assert_raises Promise::Error do + result = execute_command do + customer = Customer.new("test", plan_name: "test_usd") + Registration::Payment::InviteCode::REDIS.expect( + :get, + EMPromise.resolve(0), + ["jmp_invite_tries-test"] + ) + Registration::Payment::InviteCode::DB.expect(:transaction, []) do + raise Registration::Payment::InviteCode::Invalid, "wut" + end + Registration::Payment::InviteCode::REDIS.expect( + :incr, + EMPromise.resolve(nil), + ["jmp_invite_tries-test"] + ) + Registration::Payment::InviteCode::REDIS.expect( + :expire, + EMPromise.resolve(nil), + ["jmp_invite_tries-test", 60 * 60] + ) + Command::COMMAND_MANAGER.expect( + :write, + EMPromise.resolve( + Blather::Stanza::Iq::Command.new.tap { |iq| + iq.form.fields = [{ var: "code", value: "abc" }] + } + ), + [Matching.new do |reply| + assert_equal :form, reply.form.type + assert_nil reply.form.instructions + end] + ) + Command::COMMAND_MANAGER.expect( + :write, + EMPromise.reject(:test_result), + [Matching.new do |reply| + assert_equal :form, reply.form.type + assert_equal "wut", reply.form.instructions + end] + ) + Registration::Payment::InviteCode.new( - iq, customer, "+15555550000" - ).write.sync + ).write.catch { |e| e } end - Registration::Payment::InviteCode::COMMAND_MANAGER.verify - Registration::Payment::InviteCode::DB.verify - Registration::Payment::InviteCode::REDIS.verify + assert_equal :test_result, result + assert_mock Command::COMMAND_MANAGER + assert_mock Registration::Payment::InviteCode::DB + assert_mock Registration::Payment::InviteCode::REDIS end em :test_write_bad_code def test_write_bad_code_over_limit - customer = Customer.new("test", plan_name: "test_usd") - Registration::Payment::InviteCode::REDIS.expect( - :get, - EMPromise.resolve(11), - ["jmp_invite_tries-test"] - ) - Registration::Payment::InviteCode::COMMAND_MANAGER.expect( - :write, - EMPromise.resolve( - Blather::Stanza::Iq::Command.new.tap { |iq| - iq.form.fields = [{ var: "code", value: "abc" }] - } - ), - [Matching.new do |reply| - assert_equal :form, reply.form.type - assert_nil reply.form.instructions - end] - ) - Registration::Payment::InviteCode::REDIS.expect( - :incr, - EMPromise.resolve(nil), - ["jmp_invite_tries-test"] - ) - Registration::Payment::InviteCode::REDIS.expect( - :expire, - EMPromise.resolve(nil), - ["jmp_invite_tries-test", 60 * 60] - ) - Registration::Payment::InviteCode::COMMAND_MANAGER.expect( - :write, - EMPromise.reject(Promise::Error.new), - [Matching.new do |reply| - assert_equal :form, reply.form.type - assert_equal "Too many wrong attempts", reply.form.instructions - end] - ) - iq = Blather::Stanza::Iq::Command.new - iq.from = "test@example.com" - assert_raises Promise::Error do + result = execute_command do + customer = Customer.new("test", plan_name: "test_usd") + Registration::Payment::InviteCode::REDIS.expect( + :get, + EMPromise.resolve(11), + ["jmp_invite_tries-test"] + ) + Command::COMMAND_MANAGER.expect( + :write, + EMPromise.resolve( + Blather::Stanza::Iq::Command.new.tap { |iq| + iq.form.fields = [{ var: "code", value: "abc" }] + } + ), + [Matching.new do |reply| + assert_equal :form, reply.form.type + assert_nil reply.form.instructions + end] + ) + Registration::Payment::InviteCode::REDIS.expect( + :incr, + EMPromise.resolve(nil), + ["jmp_invite_tries-test"] + ) + Registration::Payment::InviteCode::REDIS.expect( + :expire, + EMPromise.resolve(nil), + ["jmp_invite_tries-test", 60 * 60] + ) + Command::COMMAND_MANAGER.expect( + :write, + EMPromise.reject(:test_result), + [Matching.new do |reply| + assert_equal :form, reply.form.type + assert_equal "Too many wrong attempts", reply.form.instructions + end] + ) Registration::Payment::InviteCode.new( - iq, customer, "+15555550000" - ).write.sync + ).write.catch { |e| e } end - Registration::Payment::InviteCode::COMMAND_MANAGER.verify - Registration::Payment::InviteCode::REDIS.verify + assert_equal :test_result, result + assert_mock Command::COMMAND_MANAGER + assert_mock Registration::Payment::InviteCode::REDIS end em :test_write_bad_code_over_limit end end class FinishTest < Minitest::Test - Registration::Finish::BLATHER = Minitest::Mock.new Registration::Finish::REDIS = Minitest::Mock.new BackendSgx::REDIS = Minitest::Mock.new @@ -507,7 +525,6 @@ class RegistrationTest < Minitest::Test iq = Blather::Stanza::Iq::Command.new iq.from = "test\\40example.com@cheogram.com" @finish = Registration::Finish.new( - iq, Customer.new("test", sgx: @sgx), "+15555550000" ) @@ -547,17 +564,12 @@ class RegistrationTest < Minitest::Test "Content-Type" => "application/json" } ).to_return(status: 201) - @sgx.expect( - :register!, - EMPromise.resolve(OpenStruct.new(error?: false)), - ["+15555550000"] - ) Registration::Finish::REDIS.expect( :set, nil, [ "catapult_fwd-+15555550000", - "sip:test%5C40example.com%40cheogram.com@sip.cheogram.com" + "sip:test%40example.com@sip.cheogram.com" ] ) BackendSgx::REDIS.expect( @@ -565,7 +577,8 @@ class RegistrationTest < Minitest::Test nil, ["catapult_fwd_timeout-customer_test@component", 25] ) - Registration::Finish::BLATHER.expect( + blather = Minitest::Mock.new + blather.expect( :<<, nil, [Matching.new do |reply| @@ -577,12 +590,20 @@ class RegistrationTest < Minitest::Test ) end] ) - @finish.write.sync + execute_command(blather: blather) do + @sgx.expect( + :register!, + EMPromise.resolve(OpenStruct.new(error?: false)), + ["+15555550000"] + ) + + @finish.write + end assert_requested create_order - @sgx.verify - Registration::Finish::REDIS.verify - BackendSgx::REDIS.verify - Registration::Finish::BLATHER.verify + assert_mock @sgx + assert_mock Registration::Finish::REDIS + assert_mock BackendSgx::REDIS + assert_mock blather end em :test_write @@ -605,7 +626,8 @@ class RegistrationTest < Minitest::Test FAILED RESPONSE - Registration::Finish::BLATHER.expect( + blather = Minitest::Mock.new + blather.expect( :<<, nil, [Matching.new do |reply| @@ -618,9 +640,9 @@ class RegistrationTest < Minitest::Test ) end] ) - @finish.write.sync + execute_command(blather: blather) { @finish.write } assert_requested create_order - Registration::Finish::BLATHER.verify + assert_mock blather end em :test_write_tn_fail end diff --git a/test/test_web_register_manager.rb b/test/test_web_register_manager.rb index ae6aa78..675b676 100644 --- a/test/test_web_register_manager.rb +++ b/test/test_web_register_manager.rb @@ -15,18 +15,9 @@ class WebRegisterManagerTest < Minitest::Test end def test_choose_tel_have_tel - @manager["jid@example.com"] = "+15555550000" - iq = Blather::Stanza::Iq.new - iq.from = "jid@example.com" - assert_equal [iq, "+15555550000"], @manager.choose_tel(iq).sync + jid = "jid@example.com" + @manager[jid] = "+15555550000" + assert_equal "+15555550000", @manager[jid].choose_tel.sync end em :test_choose_tel_have_tel - - def test_choose_tel_not_have_tel - skip "ChooseTel not implemented yet" - iq = Blather::Stanza::Iq.new - iq.from = "jid@example.com" - @manager.choose_tel(iq).sync - end - em :test_choose_tel_not_have_tel end -- 2.38.5