.rubocop.yml 🔗
@@ -51,6 +51,9 @@ Style/DoubleNegation:
EnforcedStyle: allowed_in_returns
Enabled: false
+Style/PerlBackrefs:
+ Enabled: false
+
Style/RegexpLiteral:
EnforcedStyle: slashes
AllowInnerSlashes: true
Christopher Vollick created
This should allow us, the admins, to query information about a customer
without having to dive in and run a couple redis queries and some
database queries before getting the full picture of who we're talking
to.
It also allows the users to request some data about themselves. Balance and
phone number are already visible in other places, but their expiry is currently
not, and people have been asking about it.
.rubocop.yml | 3
config-schema.dhall | 2
config.dhall.sample | 4
lib/api.rb | 52 ++++++++++++
lib/customer.rb | 19 ++++
lib/customer_info.rb | 87 +++++++++++++++++++++
lib/customer_info_form.rb | 82 +++++++++++++++++++
lib/customer_repo.rb | 26 +++++
lib/legacy_customer.rb | 65 +++++++++++++++
lib/proxied_jid.rb | 28 ++++++
sgx_jmp.rb | 44 ++++++++++
test/test_customer_info.rb | 114 +++++++++++++++++++++++++++
test/test_customer_info_form.rb | 117 ++++++++++++++++++++++++++++
test/test_customer_repo.rb | 144 ++++++++++++++++++++++++----------
test/test_helper.rb | 33 +++++++
test/test_proxied_jid.rb | 34 ++++++++
16 files changed, 807 insertions(+), 47 deletions(-)
@@ -51,6 +51,9 @@ Style/DoubleNegation:
EnforcedStyle: allowed_in_returns
Enabled: false
+Style/PerlBackrefs:
+ Enabled: false
+
Style/RegexpLiteral:
EnforcedStyle: slashes
AllowInnerSlashes: true
@@ -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 }
}
@@ -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"
}
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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(
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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