.rubocop.yml 🔗
@@ -14,6 +14,10 @@ Metrics/MethodLength:
Exclude:
- test/*
+Metrics/BlockLength:
+ Exclude:
+ - test/*
+
Metrics/ClassLength:
Exclude:
- test/*
Stephen Paul Weber created
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(-)
@@ -14,6 +14,10 @@ Metrics/MethodLength:
Exclude:
- test/*
+Metrics/BlockLength:
+ Exclude:
+ - test/*
+
Metrics/ClassLength:
Exclude:
- test/*
@@ -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"
@@ -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],
@@ -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
@@ -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
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class Object
+ alias then yield_self
+end
@@ -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
@@ -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
@@ -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}"])
@@ -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
@@ -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
@@ -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
</TelephoneNumberDetails>
</TelephoneNumberResponse>
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
<OrderStatus>FAILED</OrderStatus>
</OrderResponse>
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
@@ -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