diff --git a/.rubocop.yml b/.rubocop.yml index 1deee4649c684608f0cd2329ff23aa1db288f121..eb580f9ce18c4c27699bcae5d8a61846d64363a6 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -51,6 +51,9 @@ Style/DoubleNegation: EnforcedStyle: allowed_in_returns Enabled: false +Style/PerlBackrefs: + Enabled: false + Style/RegexpLiteral: EnforcedStyle: slashes AllowInnerSlashes: true diff --git a/config-schema.dhall b/config-schema.dhall index 08696aa958c7b29968e7096090ce5046675b16a8..d4617a5362b5b0cabe659281f8e95885c771c745 100644 --- a/config-schema.dhall +++ b/config-schema.dhall @@ -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 } } diff --git a/config.dhall.sample b/config.dhall.sample index 149e619cb1370ee2061a9f347aaaebd3f5997204..ae4bf1ab98cd5bd3bddc71b2d90358c26902faad 100644 --- a/config.dhall.sample +++ b/config.dhall.sample @@ -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" } diff --git a/lib/api.rb b/lib/api.rb new file mode 100644 index 0000000000000000000000000000000000000000..fab9fb33bdc8d821783770bfdb9dd19cdd472a8a --- /dev/null +++ b/lib/api.rb @@ -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 diff --git a/lib/customer.rb b/lib/customer.rb index 01f54fc9c923f4af8d2c49be3b13da37bc3d7b4f..d5e7d1c578ed29982906c6c9a7e0b7d6637c372e 100644 --- a/lib/customer.rb +++ b/lib/customer.rb @@ -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 diff --git a/lib/customer_info.rb b/lib/customer_info.rb new file mode 100644 index 0000000000000000000000000000000000000000..d3b8b38f517ccaf3ae96d57f4d2f64baa70d746a --- /dev/null +++ b/lib/customer_info.rb @@ -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 diff --git a/lib/customer_info_form.rb b/lib/customer_info_form.rb new file mode 100644 index 0000000000000000000000000000000000000000..50238f4802874e9b44daece9a55ef580135926a9 --- /dev/null +++ b/lib/customer_info_form.rb @@ -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 diff --git a/lib/customer_repo.rb b/lib/customer_repo.rb index 0f57904a9cae0f59a63bf270ad9f082773eaa5ac..642076f527c63ab3ea11911a3249d86251e062ed 100644 --- a/lib/customer_repo.rb +++ b/lib/customer_repo.rb @@ -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( diff --git a/lib/legacy_customer.rb b/lib/legacy_customer.rb new file mode 100644 index 0000000000000000000000000000000000000000..c67e329ea6b0679bc7e076102b45ee2d4ce98f66 --- /dev/null +++ b/lib/legacy_customer.rb @@ -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 diff --git a/lib/proxied_jid.rb b/lib/proxied_jid.rb new file mode 100644 index 0000000000000000000000000000000000000000..c0fd1f3cbab2e5acaee7e2df875bcd96cc2b8ab3 --- /dev/null +++ b/lib/proxied_jid.rb @@ -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 diff --git a/sgx_jmp.rb b/sgx_jmp.rb index 074cdd29b886ad005e81cbd959989f99629928e6..8e4c2bfb7fbb3075eead12d29f5ea95136b006e8 100644 --- a/sgx_jmp.rb +++ b/sgx_jmp.rb @@ -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 diff --git a/test/test_customer_info.rb b/test/test_customer_info.rb new file mode 100644 index 0000000000000000000000000000000000000000..a3840b7ea2934bc225ff52ce2b004006da361798 --- /dev/null +++ b/test/test_customer_info.rb @@ -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 diff --git a/test/test_customer_info_form.rb b/test/test_customer_info_form.rb new file mode 100644 index 0000000000000000000000000000000000000000..822f075e8db1a8eedbcf7546da919b3d0f36155a --- /dev/null +++ b/test/test_customer_info_form.rb @@ -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 diff --git a/test/test_customer_repo.rb b/test/test_customer_repo.rb index fd1804073788fc26f0f609dac93cee833cfa9abd..80df5679dc1fda467122c9f8ca8f9e40a5c0ee81 100644 --- a/test/test_customer_repo.rb +++ b/test/test_customer_repo.rb @@ -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 diff --git a/test/test_helper.rb b/test/test_helper.rb index c60e5470965ac33512f6534adc4f3476bce830e2..a61cb3219c15cfe584d05ebeff404c5fd03abccc 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -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 diff --git a/test/test_proxied_jid.rb b/test/test_proxied_jid.rb new file mode 100644 index 0000000000000000000000000000000000000000..022f7bff8630131404e59ec47086f6f80985d4fe --- /dev/null +++ b/test/test_proxied_jid.rb @@ -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