M .rubocop.yml => .rubocop.yml +3 -0
@@ 51,6 51,9 @@ Style/DoubleNegation:
EnforcedStyle: allowed_in_returns
Enabled: false
+Style/PerlBackrefs:
+ Enabled: false
+
Style/RegexpLiteral:
EnforcedStyle: slashes
AllowInnerSlashes: true
M config-schema.dhall => config-schema.dhall +2 -0
@@ 1,4 1,5 @@
{ activation_amount : Natural
+, admins : List Text
, adr : Text
, bandwidth_peer : Text
, bandwidth_site : Text
@@ 41,6 42,7 @@
, server : { host : Text, port : Natural }
, sgx : Text
, sip_host : Text
+, upstream_domain : Text
, web_register : { from : Text, to : Text }
, xep0157 : List { label : Text, value : Text, var : Text }
}
M config.dhall.sample => config.dhall.sample +3 -1
@@ 71,5 71,7 @@
adr = "",
interac = "",
payable = "",
- notify_from = "+15551234567@example.net"
+ notify_from = "+15551234567@example.net",
+ admins = ["test\\40example.com@example.net"],
+ upstream_domain = "example.net"
}
A lib/api.rb => lib/api.rb +52 -0
@@ 0,0 1,52 @@
+# frozen_string_literal: true
+
+class API
+ def self.for(customer)
+ EMPromise.all([
+ sgx_jmp?(customer),
+ api_version(customer)
+ ]).then do |is_jmp, api|
+ is_jmp ? JMP.new : api
+ end
+ end
+
+ def self.sgx_jmp?(customer)
+ key = "catapult_cred-customer_#{customer.customer_id}@jmp.chat"
+ REDIS.exists(key).then { |is_sgx| is_sgx == 1 }
+ end
+
+ def self.api_version(customer)
+ REDIS.lindex("catapult_cred-#{customer.jid}", 0).then do |api|
+ case api
+ when CONFIG.dig(:catapult, :user)
+ V1.new
+ when CONFIG.dig(:creds, :account)
+ V2.new
+ else
+ new
+ end
+ end
+ end
+
+ class V1 < API
+ def to_s
+ "v1"
+ end
+ end
+
+ class V2 < API
+ def to_s
+ "v2"
+ end
+ end
+
+ class JMP < V2
+ def to_s
+ "sgx-jmp"
+ end
+ end
+
+ def to_s
+ "not JMP"
+ end
+end
M lib/customer.rb => lib/customer.rb +19 -0
@@ 2,13 2,16 @@
require "forwardable"
+require_relative "./api"
require_relative "./blather_ext"
+require_relative "./customer_info"
require_relative "./customer_plan"
require_relative "./customer_usage"
require_relative "./backend_sgx"
require_relative "./ibr"
require_relative "./payment_methods"
require_relative "./plan"
+require_relative "./proxied_jid"
require_relative "./sip_account"
class Customer
@@ 95,5 98,21 @@ class Customer
end
end
+ def admin?
+ CONFIG[:admins].include?(jid.to_s)
+ end
+
+ def api
+ API.for(self)
+ end
+
+ def admin_info
+ AdminInfo.for(self, @plan, expires_at)
+ end
+
+ def info
+ CustomerInfo.for(self, @plan, expires_at)
+ end
+
protected def_delegator :@plan, :expires_at
end
A lib/customer_info.rb => lib/customer_info.rb +87 -0
@@ 0,0 1,87 @@
+# frozen_string_literal: true
+
+require "value_semantics/monkey_patched"
+require_relative "proxied_jid"
+require_relative "customer_plan"
+
+class CustomerInfo
+ value_semantics do
+ plan CustomerPlan
+ tel Either(String, nil)
+ balance BigDecimal
+ expires_at Either(Time, nil)
+ end
+
+ def self.for(customer, plan, expires_at)
+ customer.registered?.then do |registration|
+ new(
+ plan: plan,
+ tel: registration&.phone,
+ balance: customer.balance,
+ expires_at: expires_at
+ )
+ end
+ end
+
+ def account_status
+ if plan.plan_name.nil?
+ "Transitional"
+ elsif plan.active?
+ "Active"
+ else
+ "Expired"
+ end
+ end
+
+ def next_renewal
+ { var: "Next renewal", value: expires_at.strftime("%Y-%m-%d") } if expires_at
+ end
+
+ def fields
+ [
+ { var: "Account Status", value: account_status },
+ { var: "Phone Number", value: tel || "Not Registered" },
+ { var: "Balance", value: "$%.4f" % balance },
+ next_renewal
+ ].compact
+ end
+end
+
+class AdminInfo
+ value_semantics do
+ jid ProxiedJID, coerce: ProxiedJID.method(:new)
+ customer_id String
+ info CustomerInfo
+ api API
+ end
+
+ def self.for(customer, plan, expires_at)
+ EMPromise.all([
+ CustomerInfo.for(customer, plan, expires_at),
+ customer.api
+ ]).then do |info, api_value|
+ new(
+ jid: customer.jid,
+ customer_id: customer.customer_id,
+ info: info, api: api_value
+ )
+ end
+ end
+
+ def plan_fields
+ [
+ { var: "Plan", value: info.plan.plan_name || "No Plan" },
+ { var: "Currency", value: (info.plan.currency || "No Currency").to_s }
+ ]
+ end
+
+ def fields
+ info.fields + [
+ { var: "JID", value: jid.unproxied.to_s },
+ { var: "Cheo JID", value: jid.to_s },
+ { var: "Customer ID", value: customer_id },
+ *plan_fields,
+ { var: "API", value: api.to_s }
+ ]
+ end
+end
A lib/customer_info_form.rb => lib/customer_info_form.rb +82 -0
@@ 0,0 1,82 @@
+# frozen_string_literal: true
+
+require_relative "customer_repo"
+require_relative "proxied_jid"
+require_relative "legacy_customer"
+
+class CustomerInfoForm
+ def initialize(customer_repo=CustomerRepo.new)
+ @customer_repo = customer_repo
+ end
+
+ def picker_form
+ form = Blather::Stanza::X.new(:form)
+ form.title = "Pick Customer"
+ form.instructions = "Tell us something about the customer and we'll try " \
+ "to get more information for you"
+
+ form.fields = {
+ var: "q", type: "text-single",
+ label: "Something about the customer",
+ description: "Supported things include: customer ID, JID, phone number"
+ }
+
+ form
+ end
+
+ def find_customer(response)
+ parse_something(response.form.field("q").value)
+ end
+
+ class NoCustomer
+ class AdminInfo
+ def fields
+ [{ var: "Account Status", value: "Not Found" }]
+ end
+ end
+
+ def admin_info
+ AdminInfo.new
+ end
+ end
+
+ def parse_something(value)
+ parser = Parser.new(@customer_repo)
+
+ EMPromise.all([
+ parser.as_customer_id(value),
+ parser.as_jid(value),
+ parser.as_phone(value),
+ EMPromise.resolve(NoCustomer.new)
+ ]).then { |approaches| approaches.compact.first }
+ end
+
+ class Parser
+ def initialize(customer_repo)
+ @customer_repo = customer_repo
+ end
+
+ def as_customer_id(value)
+ @customer_repo.find(value).catch { nil }
+ end
+
+ def as_cheo(value)
+ ProxiedJID.proxy(Blather::JID.new(value))
+ end
+
+ def as_jid(value)
+ EMPromise.all([
+ @customer_repo.find_by_jid(value).catch { nil },
+ @customer_repo.find_by_jid(as_cheo(value)).catch { nil }
+ ]).then { |approaches| approaches.compact.first }
+ end
+
+ def as_phone(value)
+ unless value.gsub(/[^0-9]/, "") =~ /^\+?1?(\d{10})$/
+ return EMPromise.resolve(nil)
+ end
+
+ @customer_repo.find_by_tel("+1#{$1}").catch { nil }
+ end
+ end
+end
M lib/customer_repo.rb => lib/customer_repo.rb +23 -3
@@ 1,6 1,7 @@
# frozen_string_literal: true
require_relative "customer"
+require_relative "legacy_customer"
require_relative "polyfill"
class CustomerRepo
@@ 18,9 19,21 @@ class CustomerRepo
end
def find_by_jid(jid)
- @redis.get("jmp_customer_id-#{jid}").then do |customer_id|
- raise "No customer id" unless customer_id
- find_inner(customer_id, jid)
+ if jid.to_s =~ /\Acustomer_(.+)@jmp.chat\Z/
+ find($1)
+ else
+ @redis.get("jmp_customer_id-#{jid}").then { |customer_id|
+ raise "No customer id" unless customer_id
+ find_inner(customer_id, jid)
+ }.catch do
+ find_legacy_customer(jid)
+ end
+ end
+ end
+
+ def find_by_tel(tel)
+ @redis.get("catapult_jid-#{tel}").then do |jid|
+ find_by_jid(jid)
end
end
@@ 39,6 52,13 @@ class CustomerRepo
protected
+ def find_legacy_customer(jid)
+ @redis.lindex("catapult_cred-#{jid}", 3).then do |tel|
+ raise "No customer" unless tel
+ LegacyCustomer.new(Blather::JID.new(jid), tel)
+ end
+ end
+
def hydrate_plan(customer_id, raw_customer)
raw_customer.dup.tap do |data|
data[:plan] = CustomerPlan.new(
A lib/legacy_customer.rb => lib/legacy_customer.rb +65 -0
@@ 0,0 1,65 @@
+# frozen_string_literal: true
+
+require "value_semantics/monkey_patched"
+require_relative "proxied_jid"
+
+class LegacyCustomer
+ attr_reader :jid, :tel
+
+ def initialize(jid, tel)
+ @jid = jid
+ @tel = tel
+ end
+
+ def customer_id
+ nil
+ end
+
+ def info
+ EMPromise.resolve(nil).then do
+ Info.new(jid: jid, tel: tel)
+ end
+ end
+
+ def admin_info
+ EMPromise.all([
+ info,
+ api
+ ]).then do |info, api|
+ AdminInfo.new(info: info, api: api)
+ end
+ end
+
+ def api
+ API.for(self)
+ end
+
+ class Info
+ value_semantics do
+ jid ProxiedJID, coerce: ProxiedJID.method(:new)
+ tel String
+ end
+
+ def fields
+ [
+ { var: "JID", value: jid.unproxied.to_s },
+ { var: "Phone Number", value: tel }
+ ]
+ end
+ end
+
+ class AdminInfo
+ value_semantics do
+ info Info
+ api API
+ end
+
+ def fields
+ info.fields + [
+ { var: "Account Status", value: "Legacy" },
+ { var: "Cheo JID", value: info.jid.to_s },
+ { var: "API", value: api.to_s }
+ ]
+ end
+ end
+end
A lib/proxied_jid.rb => lib/proxied_jid.rb +28 -0
@@ 0,0 1,28 @@
+# frozen_string_literal: true
+
+require "delegate"
+require "blather"
+
+class ProxiedJID < SimpleDelegator
+ ESCAPED = /20|22|26|27|2f|3a|3c|3e|40|5c/
+ def unproxied
+ Blather::JID.new(
+ node.gsub(/\\(#{ESCAPED})/) { |s|
+ s[1..-1].to_i(16).chr
+ }
+ )
+ end
+
+ def self.proxy(jid, suffix=CONFIG[:upstream_domain])
+ ProxiedJID.new(
+ Blather::JID.new(
+ jid.stripped.to_s
+ .gsub(/([ "&'\/:<>@]|\\(?=#{ESCAPED}))/) { |s|
+ "\\#{s.ord.to_s(16)}"
+ },
+ suffix,
+ jid.resource
+ )
+ )
+ end
+end
M sgx_jmp.rb => sgx_jmp.rb +44 -0
@@ 68,6 68,7 @@ require_relative "lib/buy_account_credit_form"
require_relative "lib/command"
require_relative "lib/command_list"
require_relative "lib/customer"
+require_relative "lib/customer_info_form"
require_relative "lib/customer_repo"
require_relative "lib/electrum"
require_relative "lib/expiring_lock"
@@ 101,6 102,8 @@ def new_sentry_hub(stanza, name: nil)
hub
end
+class AuthError < StandardError; end
+
# Braintree is not async, so wrap in EM.defer for now
class AsyncBraintree
def initialize(environment:, merchant_id:, public_key:, private_key:, **)
@@ 553,6 556,47 @@ command :execute?, node: "web-register", sessionid: nil do |iq|
end
end
+Command.new(
+ "info",
+ "Show Account Info",
+ list_for: ->(*) { true }
+) {
+ Command.customer.then(&:info).then do |info|
+ Command.finish do |reply|
+ form = Blather::Stanza::X.new(:result)
+ form.title = "Account Info"
+ form.fields = info.fields
+ reply.command << form
+ end
+ end
+}.register(self).then(&CommandList.method(:register))
+
+Command.new(
+ "customer info",
+ "Show Customer Info",
+ list_for: ->(customer: nil, **) { customer&.admin? }
+) {
+ Command.customer.then do |customer|
+ raise AuthError, "You are not an admin" unless customer&.admin?
+
+ customer_info = CustomerInfoForm.new
+ Command.reply { |reply|
+ reply.command << customer_info.picker_form
+ }.then { |response|
+ customer_info.find_customer(response)
+ }.then do |target_customer|
+ target_customer.admin_info.then do |info|
+ Command.finish do |reply|
+ form = Blather::Stanza::X.new(:result)
+ form.title = "Customer Info"
+ form.fields = info.fields
+ reply.command << form
+ end
+ end
+ end
+ end
+}.register(self).then(&CommandList.method(:register))
+
command sessionid: /./ do |iq|
COMMAND_MANAGER.fulfill(iq)
end
A test/test_customer_info.rb => test/test_customer_info.rb +114 -0
@@ 0,0 1,114 @@
+# frozen_string_literal: true
+
+require "test_helper"
+
+API::REDIS = Minitest::Mock.new
+
+class CustomerInfoTest < Minitest::Test
+ def test_info_does_not_crash
+ sgx = Minitest::Mock.new
+ sgx.expect(:registered?, EMPromise.resolve(nil))
+
+ cust = customer(sgx: sgx)
+ assert cust.info.sync.fields
+ assert_mock sgx
+ end
+ em :test_info_does_not_crash
+
+ def test_admin_info_does_not_crash
+ sgx = Minitest::Mock.new
+ sgx.expect(:registered?, EMPromise.resolve(nil))
+
+ API::REDIS.expect(
+ :exists,
+ EMPromise.resolve(nil),
+ ["catapult_cred-customer_test@jmp.chat"]
+ )
+
+ API::REDIS.expect(
+ :lindex,
+ EMPromise.resolve(nil),
+ ["catapult_cred-test@example.net", 0]
+ )
+
+ cust = customer(sgx: sgx)
+ assert cust.admin_info.sync.fields
+ assert_mock sgx
+ end
+ em :test_admin_info_does_not_crash
+
+ def test_inactive_info_does_not_crash
+ sgx = Minitest::Mock.new
+ sgx.expect(:registered?, EMPromise.resolve(nil))
+
+ plan = CustomerPlan.new("test", plan: nil, expires_at: nil)
+ cust = Customer.new(
+ "test",
+ Blather::JID.new("test@example.net"),
+ plan: plan,
+ sgx: sgx
+ )
+ assert cust.info.sync.fields
+ assert_mock sgx
+ end
+ em :test_inactive_info_does_not_crash
+
+ def test_inactive_admin_info_does_not_crash
+ sgx = Minitest::Mock.new
+ sgx.expect(:registered?, EMPromise.resolve(nil))
+
+ API::REDIS.expect(
+ :exists,
+ EMPromise.resolve(nil),
+ ["catapult_cred-customer_test@jmp.chat"]
+ )
+
+ API::REDIS.expect(
+ :lindex,
+ EMPromise.resolve(nil),
+ ["catapult_cred-test@example.net", 0]
+ )
+
+ plan = CustomerPlan.new("test", plan: nil, expires_at: nil)
+ cust = Customer.new(
+ "test",
+ Blather::JID.new("test@example.net"),
+ plan: plan,
+ sgx: sgx
+ )
+
+ assert cust.admin_info.sync.fields
+ assert_mock sgx
+ end
+ em :test_inactive_admin_info_does_not_crash
+
+ def test_legacy_customer_info_does_not_crash
+ cust = LegacyCustomer.new(
+ Blather::JID.new("legacy@example.com"),
+ "+12223334444"
+ )
+ assert cust.info.sync.fields
+ end
+ em :test_legacy_customer_info_does_not_crash
+
+ def test_legacy_customer_admin_info_does_not_crash
+ API::REDIS.expect(
+ :exists,
+ EMPromise.resolve(nil),
+ ["catapult_cred-customer_@jmp.chat"]
+ )
+
+ API::REDIS.expect(
+ :lindex,
+ EMPromise.resolve(nil),
+ ["catapult_cred-legacy@example.com", 0]
+ )
+
+ cust = LegacyCustomer.new(
+ Blather::JID.new("legacy@example.com"),
+ "+12223334444"
+ )
+ assert cust.admin_info.sync.fields
+ end
+ em :test_legacy_customer_admin_info_does_not_crash
+end
A test/test_customer_info_form.rb => test/test_customer_info_form.rb +117 -0
@@ 0,0 1,117 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require "forwardable"
+require "customer_info_form"
+require "customer_repo"
+
+class FakeRepo
+ def initialize(customers)
+ @customers = customers
+ end
+
+ def find(id)
+ EMPromise.resolve(nil).then do
+ @customers.find { |cust| cust.customer_id == id } || raise("No Customer")
+ end
+ end
+
+ def find_by_jid(jid)
+ EMPromise.resolve(nil).then do
+ @customers.find { |cust| cust.jid.to_s == jid.to_s } || raise("No Customer")
+ end
+ end
+
+ def find_by_tel(tel)
+ EMPromise.resolve(nil).then do
+ @customers.find { |cust| cust.tel == tel } || raise("No Customer")
+ end
+ end
+end
+
+class CustomerInfoFormTest < Minitest::Test
+ def setup
+ @customer_test = OpenStruct.new(
+ customer_id: "test",
+ jid: "test\\40example.com@example.net",
+ tel: "+13334445555"
+ )
+ @customer_v2 = OpenStruct.new(
+ customer_id: "test_v2",
+ jid: "test_v2\\40example.com@example.net",
+ tel: "+14445556666"
+ )
+ @repo = FakeRepo.new([@customer_test, @customer_v2])
+ @info_form = CustomerInfoForm.new(@repo)
+ end
+
+ def test_nothing
+ assert_kind_of(
+ CustomerInfoForm::NoCustomer,
+ @info_form.parse_something("").sync
+ )
+ end
+ em :test_nothing
+
+ def test_find_customer_id
+ result = @info_form.parse_something("test").sync
+ assert_equal @customer_test, result
+ end
+ em :test_find_customer_id
+
+ def test_find_real_jid
+ result = @info_form.parse_something("test@example.com").sync
+ assert_equal @customer_test, result
+ end
+ em :test_find_real_jid
+
+ def test_find_cheo_jid
+ result = @info_form.parse_something(
+ "test\\40example.com@example.net"
+ ).sync
+ assert_equal @customer_test, result
+ end
+ em :test_find_cheo_jid
+
+ def test_find_sgx_jmp_customer_by_phone
+ result = @info_form.parse_something("+13334445555").sync
+ assert_equal @customer_test, result
+ end
+ em :test_find_sgx_jmp_customer_by_phone
+
+ def test_find_sgx_jmp_customer_by_phone_friendly_format
+ result = @info_form.parse_something("13334445555").sync
+ assert_equal @customer_test, result
+
+ result = @info_form.parse_something("3334445555").sync
+ assert_equal @customer_test, result
+
+ result = @info_form.parse_something("(333) 444-5555").sync
+ assert_equal @customer_test, result
+ end
+ em :test_find_sgx_jmp_customer_by_phone_friendly_format
+
+ def test_find_v2_customer_by_phone
+ result = @info_form.parse_something("+14445556666").sync
+ assert_equal @customer_v2, result
+ end
+ em :test_find_v2_customer_by_phone
+
+ def test_missing_customer_by_phone
+ result = @info_form.parse_something("+17778889999").sync
+ assert_kind_of(
+ CustomerInfoForm::NoCustomer,
+ result
+ )
+ end
+ em :test_missing_customer_by_phone
+
+ def test_garbage
+ result = @info_form.parse_something("garbage").sync
+ assert_kind_of(
+ CustomerInfoForm::NoCustomer,
+ result
+ )
+ end
+ em :test_garbage
+end
M test/test_customer_repo.rb => test/test_customer_repo.rb +102 -42
@@ 4,69 4,129 @@ require "test_helper"
require "customer_repo"
class CustomerRepoTest < Minitest::Test
+ FAKE_REDIS = FakeRedis.new(
+ # sgx-jmp customer
+ "jmp_customer_jid-test" => "test@example.com",
+ "jmp_customer_id-test@example.com" => "test",
+ "catapult_jid-+13334445555" => "customer_test@jmp.chat",
+ "catapult_cred-customer_test@jmp.chat" => [
+ "test_bw_customer", "", "", "+13334445555"
+ ],
+ # sgx-jmp customer, empty DB
+ "jmp_customer_jid-empty" => "empty@example.com",
+ "jmp_customer_id-empty@example.com" => "empty",
+ "catapult_jid-+16667778888" => "customer_empty@jmp.chat",
+ "catapult_cred-customer_empty@jmp.chat" => [
+ "test_bw_customer", "", "", "+16667778888"
+ ],
+ # v2 customer
+ "jmp_customer_jid-test_v2" => "test_v2@example.com",
+ "jmp_customer_id-test_v2@example.com" => "test_v2",
+ "catapult_jid-+14445556666" => "test_v2@example.com",
+ "catapult_cred-test_v2@example.com" => [
+ "test_bw_customer", "", "", "+14445556666"
+ ],
+ # legacy customer
+ "catapult_cred-legacy@example.com" => [
+ "catapult_user", "", "", "+12223334444"
+ ],
+ "catapult_jid-+12223334444" => "legacy@example.com"
+ )
+
+ FAKE_DB = FakeDB.new(
+ ["test"] => [{
+ "balance" => BigDecimal(1234),
+ "plan_name" => "test_usd",
+ "expires_at" => Time.now + 100
+ }],
+ ["test_v2"] => [{
+ "balance" => BigDecimal(2345),
+ "plan_name" => "test_usd",
+ "expires_at" => Time.now + 100
+ }]
+ )
+
def mkrepo(
- redis: Minitest::Mock.new,
- db: Minitest::Mock.new,
+ redis: FAKE_REDIS,
+ db: FAKE_DB,
braintree: Minitest::Mock.new
)
CustomerRepo.new(redis: redis, db: db, braintree: braintree)
end
+ def setup
+ @repo = mkrepo
+ end
+
def test_find_by_jid
- redis = Minitest::Mock.new
- db = Minitest::Mock.new
- repo = mkrepo(redis: redis, db: db)
- redis.expect(
- :get,
- EMPromise.resolve(1),
- ["jmp_customer_id-test@example.com"]
- )
- db.expect(
- :query_defer,
- EMPromise.resolve([{ balance: 1234, plan_name: "test_usd" }]),
- [String, [1]]
- )
- customer = repo.find_by_jid("test@example.com").sync
+ customer = @repo.find_by_jid("test@example.com").sync
assert_kind_of Customer, customer
assert_equal 1234, customer.balance
assert_equal "merchant_usd", customer.merchant_account
- assert_mock redis
- assert_mock db
end
em :test_find_by_jid
+ def test_find_by_id
+ customer = @repo.find("test").sync
+ assert_kind_of Customer, customer
+ assert_equal 1234, customer.balance
+ assert_equal "merchant_usd", customer.merchant_account
+ end
+ em :test_find_by_id
+
+ def test_find_by_customer_jid
+ customer = @repo.find_by_jid("customer_test@jmp.chat").sync
+ assert_kind_of Customer, customer
+ assert_equal 1234, customer.balance
+ assert_equal "merchant_usd", customer.merchant_account
+ end
+ em :test_find_by_customer_jid
+
def test_find_by_jid_not_found
- redis = Minitest::Mock.new
- repo = mkrepo(redis: redis)
- redis.expect(
- :get,
- EMPromise.resolve(nil),
- ["jmp_customer_id-test2@example.com"]
- )
assert_raises do
- repo.find_by_jid("test2@example.com").sync
+ @repo.find_by_jid("test2@example.com").sync
end
- assert_mock redis
end
em :test_find_by_jid_not_found
+ def test_find_legacy_customer
+ customer = @repo.find_by_jid("legacy@example.com").sync
+ assert_kind_of LegacyCustomer, customer
+ assert_equal "+12223334444", customer.tel
+ end
+ em :test_find_legacy_customer
+
+ def test_find_sgx_customer_by_phone
+ customer = @repo.find_by_tel("+13334445555").sync
+ assert_kind_of Customer, customer
+ assert_equal "test", customer.customer_id
+ end
+ em :test_find_sgx_customer_by_phone
+
+ def test_find_v2_customer_by_phone
+ customer = @repo.find_by_tel("+14445556666").sync
+ assert_kind_of Customer, customer
+ assert_equal "test_v2", customer.customer_id
+ end
+ em :test_find_v2_customer_by_phone
+
+ def test_find_legacy_customer_by_phone
+ customer = @repo.find_by_tel("+12223334444").sync
+ assert_kind_of LegacyCustomer, customer
+ assert_equal "legacy@example.com", customer.jid.to_s
+ end
+ em :test_find_legacy_customer_by_phone
+
+ def test_find_missing_phone
+ assert_raises do
+ @repo.find_by_tel("+15556667777").sync
+ end
+ end
+ em :test_find_missing_phone
+
def test_find_db_empty
- db = Minitest::Mock.new
- redis = Minitest::Mock.new
- redis.expect(
- :get,
- EMPromise.resolve("test@example.net"),
- ["jmp_customer_jid-7357"]
- )
- repo = mkrepo(db: db, redis: redis)
- db.expect(
- :query_defer,
- EMPromise.resolve([]),
- [String, [7357]]
- )
- customer = repo.find(7357).sync
+ customer = @repo.find("empty").sync
assert_equal BigDecimal(0), customer.balance
- assert_mock db
end
em :test_find_db_empty
M test/test_helper.rb => test/test_helper.rb +32 -1
@@ 94,7 94,8 @@ CONFIG = {
}
},
credit_card_url: ->(*) { "http://creditcard.example.com" },
- electrum_notify_url: ->(*) { "http://notify.example.com" }
+ electrum_notify_url: ->(*) { "http://notify.example.com" },
+ upstream_domain: "example.net"
}.freeze
def panic(e)
@@ 131,6 132,36 @@ class PromiseMock < Minitest::Mock
end
end
+class FakeRedis
+ def initialize(values)
+ @values = values
+ end
+
+ def get(key)
+ EMPromise.resolve(@values[key])
+ end
+
+ def exists(*keys)
+ EMPromise.resolve(
+ @values.select { |k, _| keys.include? k }.size
+ )
+ end
+
+ def lindex(key, index)
+ get(key).then { |v| v&.fetch(index) }
+ end
+end
+
+class FakeDB
+ def initialize(items)
+ @items = items
+ end
+
+ def query_defer(_, args)
+ EMPromise.resolve(@items.fetch(args, []))
+ end
+end
+
module EventMachine
class << self
# Patch EM.add_timer to be instant in tests
A test/test_proxied_jid.rb => test/test_proxied_jid.rb +34 -0
@@ 0,0 1,34 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require "proxied_jid"
+
+class ProxiedJIDTest < Minitest::Test
+ def test_unproxied
+ jid = ProxiedJID.new(Blather::JID.new("test\\40example.com@example.net"))
+ assert_equal "test@example.com", jid.unproxied.to_s
+ end
+
+ def test_proxied
+ jid = ProxiedJID.proxy(Blather::JID.new("test@example.com"))
+ assert_equal "test\\40example.com@example.net", jid.to_s
+ end
+
+ def test_escape
+ jid = ProxiedJID.proxy(Blather::JID.new("test \"&'/:<>", "example.com"))
+ assert_equal(
+ "test\\20\\22\\26\\27\\2f\\3a\\3c\\3e\\40example.com@example.net",
+ jid.to_s
+ )
+ end
+
+ def test_backlash_necessary
+ jid = ProxiedJID.proxy(Blather::JID.new("moop\\27@example.com"))
+ assert_equal "moop\\5c27\\40example.com@example.net", jid.to_s
+ end
+
+ def test_backslash_unnecessary
+ jid = ProxiedJID.proxy(Blather::JID.new("moop\\things@example.com"))
+ assert_equal "moop\\things\\40example.com@example.net", jid.to_s
+ end
+end