From f6e362c88a37998c14212ac45ad218268eac6ab0 Mon Sep 17 00:00:00 2001 From: Stephen Paul Weber Date: Tue, 28 Jun 2022 16:35:22 -0500 Subject: [PATCH] Initial test framework and some tests --- Gemfile | 17 ++- Rakefile | 21 ++++ sgx-bwmsgsv2.rb | 7 +- test/test_component.rb | 256 +++++++++++++++++++++++++++++++++++++++++ test/test_helper.rb | 138 ++++++++++++++++++++++ 5 files changed, 433 insertions(+), 6 deletions(-) create mode 100644 Rakefile create mode 100644 test/test_component.rb create mode 100644 test/test_helper.rb diff --git a/Gemfile b/Gemfile index b4c0a64e912aec1f255c1c937cb38136a3fa9173..6f07ae52cfe90ef287ef4619ff82c8365e9fd6d2 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,7 @@ source 'https://rubygems.org' gem 'activesupport', '<5.0.0' -gem 'blather' +gem 'blather', git: "https://github.com/adhearsion/blather.git" gem 'em-hiredis' gem 'em-http-request' gem 'em_promise.rb' @@ -15,6 +15,17 @@ gem 'rack', '< 2' gem 'redis' gem "sentry-ruby", "<= 4.3.1" -group :development do - gem 'rubocop', require: false +group(:development) do + gem "pry-reload" + gem "pry-rescue" + gem "pry-stack_explorer" +end + +group(:test) do + gem 'minitest' + gem 'rack-test' + gem 'rake' + gem 'rubocop' + gem 'simplecov', require: false + gem 'webmock' end diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000000000000000000000000000000000000..e76316c23c6a9119099dda2cc10fd68658c99a09 --- /dev/null +++ b/Rakefile @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require "rake/testtask" +require "rubocop/rake_task" + +Rake::TestTask.new(:test) do |t| + ENV["ENV"] = "test" + + t.libs << "test" + t.libs << "lib" + t.test_files = FileList["test/**/test_*.rb"] + t.warning = false +end + +RuboCop::RakeTask.new(:lint) + +task :entr do + sh "sh", "-c", "git ls-files | entr -s 'rake test && rubocop'" +end + +task default: :test diff --git a/sgx-bwmsgsv2.rb b/sgx-bwmsgsv2.rb index bf7c12b97149ac74699a95897a918cdd945b237a..8fd20c2c572dc127dcfbf405707a15d70724fb3f 100755 --- a/sgx-bwmsgsv2.rb +++ b/sgx-bwmsgsv2.rb @@ -76,6 +76,7 @@ protected def wrap_handler(*args) v = yield(*args) + v = v.sync if ENV['ENV'] == 'test' && v.is_a?(Promise) v.catch(&method(:panic)) if v.is_a?(Promise) true # Do not run other handlers unless throw :pass rescue Exception => e @@ -593,7 +594,7 @@ module SGXbwmsgsv2 creds_from_registration_query(qn) end }.then { |user_id, api_token, api_secret, phone_num| - if phone_num[0] == '+' + if phone_num && phone_num[0] == '+' [user_id, api_token, api_secret, phone_num] else # TODO: add text re number not (yet) supported @@ -724,7 +725,7 @@ module SGXbwmsgsv2 # Unknown IQ, ignore for now EMPromise.reject(:done) end.catch { |e| - if e.is_a?(Array) && e.length == 2 + if e.is_a?(Array) && (e.length == 2 || e.length == 3) write_to_stream error_msg(i.reply, qn, *e) elsif e != :done EMPromise.reject(e) @@ -1051,4 +1052,4 @@ at_exit do end end end -end +end unless ENV['ENV'] == 'test' diff --git a/test/test_component.rb b/test/test_component.rb new file mode 100644 index 0000000000000000000000000000000000000000..276f30fda4c77a696aa3f86a0d30f4562d2b0b6b --- /dev/null +++ b/test/test_component.rb @@ -0,0 +1,256 @@ +# frozen_string_literal: true + +require "test_helper" +require_relative "../sgx-bwmsgsv2" + +def panic(e) + $panic = e +end + +class ComponentTest < Minitest::Test + def setup + SGXbwmsgsv2.instance_variable_set(:@written, []) + + def SGXbwmsgsv2.write_to_stream(s) + @written ||= [] + @written << s + end + + REDIS.set("catapult_cred-test@example.com", [ + 'account', 'user', 'password', '+15550000000' + ]) + end + + def written + SGXbwmsgsv2.instance_variable_get(:@written) + end + + def xmpp_error_name(error) + error.find_first( + "child::*[name()!='text']", + Blather::StanzaError::STANZA_ERR_NS + ).element_name + end + + def process_stanza(s) + SGXbwmsgsv2.send(:client).receive_data(s) + raise $panic if $panic + end + + def test_message_unregistered + m = Blather::Stanza::Message.new("+15551234567@component", "a"*4096) + m.from = "unknown@example.com" + process_stanza(m) + + assert_equal 1, written.length + + stanza = Blather::XMPPNode.parse(written.first.to_xml) + assert stanza.error? + error = stanza.find_first("error") + assert_equal "auth", error["type"] + assert_equal "registration-required", xmpp_error_name(error) + end + em :test_message_unregistered + + def test_message_too_long + req = stub_request( + :post, + "https://messaging.bandwidth.com/api/v2/users/account/messages" + ).with(body: { + from: "+15550000000", + to: "+15551234567", + text: "a"*4096, + applicationId: nil, + tag: " " + }).to_return(status: 400) + + m = Blather::Stanza::Message.new("+15551234567@component", "a"*4096) + m.from = "test@example.com" + process_stanza(m) + + assert_requested req + assert_equal 1, written.length + + stanza = Blather::XMPPNode.parse(written.first.to_xml) + assert stanza.error? + error = stanza.find_first("error") + assert_equal "cancel", error["type"] + assert_equal "internal-server-error", xmpp_error_name(error) + end + em :test_message_too_long + + def test_message_to_component_not_group + m = Blather::Stanza::Message.new("component", "a"*4096) + m.from = "test@example.com" + process_stanza(m) + + assert_equal 1, written.length + + stanza = Blather::XMPPNode.parse(written.first.to_xml) + assert stanza.error? + error = stanza.find_first("error") + assert_equal "cancel", error["type"] + assert_equal "item-not-found", xmpp_error_name(error) + end + em :test_message_to_component_not_group + + def test_message_to_invalid_num + m = Blather::Stanza::Message.new("123@component", "a"*4096) + m.from = "test@example.com" + process_stanza(m) + + assert_equal 1, written.length + + stanza = Blather::XMPPNode.parse(written.first.to_xml) + assert stanza.error? + error = stanza.find_first("error") + assert_equal "cancel", error["type"] + assert_equal "item-not-found", xmpp_error_name(error) + end + em :test_message_to_invalid_num + + def test_message_to_anonymous + m = Blather::Stanza::Message.new( + "1;phone-context=anonymous.phone-context.soprani.ca@component", + "a"*4096 + ) + m.from = "test@example.com" + process_stanza(m) + + assert_equal 1, written.length + + stanza = Blather::XMPPNode.parse(written.first.to_xml) + assert stanza.error? + error = stanza.find_first("error") + assert_equal "cancel", error["type"] + assert_equal "gone", xmpp_error_name(error) + end + em :test_message_to_anonymous + + def test_blank_message + m = Blather::Stanza::Message.new("+15551234567@component", " ") + m.from = "test@example.com" + process_stanza(m) + + assert_equal 1, written.length + + stanza = Blather::XMPPNode.parse(written.first.to_xml) + assert stanza.error? + error = stanza.find_first("error") + assert_equal "modify", error["type"] + assert_equal "policy-violation", xmpp_error_name(error) + end + em :test_blank_message + + def test_ibr_bad_tel + iq = Blather::Stanza::Iq::IBR.new(:set, "component") + iq.from = "newuser@example.com" + iq.phone = "5551234567" + process_stanza(iq) + + assert_equal 1, written.length + + stanza = Blather::XMPPNode.parse(written.first.to_xml) + assert stanza.error? + error = stanza.find_first("error") + assert_equal "cancel", error["type"] + assert_equal "item-not-found", xmpp_error_name(error) + end + em :test_ibr_bad_tel + + def test_ibr_bad_creds + stub_request( + :get, + "https://messaging.bandwidth.com/api/v2/users/acct/media" + ).with(basic_auth: ["user", "pw"]).to_return(status: 401) + + iq = Blather::Stanza::Iq::IBR.new(:set, "component") + iq.from = "newuser@example.com" + iq.phone = "+15551234567" + iq.nick = "acct" + iq.username = "user" + iq.password = "pw" + process_stanza(iq) + + assert_equal 1, written.length + + stanza = Blather::XMPPNode.parse(written.first.to_xml) + assert stanza.error? + error = stanza.find_first("error") + assert_equal "auth", error["type"] + assert_equal "not-authorized", xmpp_error_name(error) + end + em :test_ibr_bad_creds + + def test_ibr_number_not_found + stub_request( + :get, + "https://messaging.bandwidth.com/api/v2/users/acct/media" + ).with(basic_auth: ["user", "pw"]).to_return(status: 404) + + iq = Blather::Stanza::Iq::IBR.new(:set, "component") + iq.from = "newuser@example.com" + iq.phone = "+15551234567" + iq.nick = "acct" + iq.username = "user" + iq.password = "pw" + process_stanza(iq) + + assert_equal 1, written.length + + stanza = Blather::XMPPNode.parse(written.first.to_xml) + assert stanza.error? + error = stanza.find_first("error") + assert_equal "cancel", error["type"] + assert_equal "item-not-found", xmpp_error_name(error) + end + em :test_ibr_number_not_found + + def test_ibr_other_error + stub_request( + :get, + "https://messaging.bandwidth.com/api/v2/users/acct/media" + ).with(basic_auth: ["user", "pw"]).to_return(status: 400) + + iq = Blather::Stanza::Iq::IBR.new(:set, "component") + iq.from = "newuser@example.com" + iq.phone = "+15551234567" + iq.nick = "acct" + iq.username = "user" + iq.password = "pw" + process_stanza(iq) + + assert_equal 1, written.length + + stanza = Blather::XMPPNode.parse(written.first.to_xml) + assert stanza.error? + error = stanza.find_first("error") + assert_equal "modify", error["type"] + assert_equal "not-acceptable", xmpp_error_name(error) + end + em :test_ibr_other_error + + def test_ibr_conflict + stub_request( + :get, + "https://messaging.bandwidth.com/api/v2/users/acct/media" + ).with(basic_auth: ["user", "pw"]).to_return(status: 200, body: "[]") + + iq = Blather::Stanza::Iq::IBR.new(:set, "component") + iq.from = "test@example.com" + iq.phone = "+15550000000" + iq.nick = "acct" + iq.username = "user" + iq.password = "pw" + process_stanza(iq) + + assert_equal 1, written.length + + stanza = Blather::XMPPNode.parse(written.first.to_xml) + assert stanza.error? + error = stanza.find_first("error") + assert_equal "cancel", error["type"] + assert_equal "conflict", xmpp_error_name(error) + end + em :test_ibr_conflict +end diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 0000000000000000000000000000000000000000..acde8af58ac4c0ed2968f69b9469af8442b3a39a --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,138 @@ +# frozen_string_literal: true + +require "simplecov" +SimpleCov.start do + add_filter "/test/" + enable_coverage :branch +end + +require "minitest/autorun" +require "webmock/minitest" + +begin + require "pry-rescue/minitest" + require "pry-reload" + + module Minitest + class Test + alias old_capture_exceptions capture_exceptions + def capture_exceptions + old_capture_exceptions do + yield + rescue Minitest::Skip => e + failures << e + end + end + end + end +rescue LoadError + # Just helpers for dev, no big deal if missing + nil +end + +$VERBOSE = nil +ARGV[0] = "component" + +class FakeRedis + def initialize(values={}) + @values = values + end + + def set(key, value, *) + @values[key] = value + EMPromise.resolve("OK") + end + + def setex(key, _expiry, value) + set(key, value) + end + + def mget(*keys) + EMPromise.all(keys.map(&method(:get))) + end + + def get(key) + EMPromise.resolve(@values[key]) + end + + def getbit(key, bit) + get(key).then { |v| v.to_i.to_s(2)[bit].to_i } + end + + def bitfield(key, *ops) + get(key).then do |v| + bits = v.to_i.to_s(2) + ops.each_slice(3).map do |(op, encoding, offset)| + raise "unsupported bitfield op" unless op == "GET" + raise "unsupported bitfield op" unless encoding == "u1" + + bits[offset].to_i + end + end + end + + def hget(key, field) + @values.dig(key, field) + end + + def hincrby(key, field, incrby) + @values[key] ||= {} + @values[key][field] ||= 0 + @values[key][field] += incrby + end + + def sadd(key, member) + @values[key] ||= Set.new + @values[key] << member + end + + def srem(key, member) + @values[key].delete(member) + end + + def scard(key) + @values[key]&.size || 0 + end + + def expire(_, _); end + + def exists(*keys) + EMPromise.resolve( + @values.select { |k, _| keys.include? k }.size.to_s + ) + end + + def lindex(key, index) + get(key).then { |v| v&.fetch(index) } + end + + def lrange(key, sindex, eindex) + get(key).then { |v| v ? v[sindex..eindex] : [] } + end +end + +REDIS = FakeRedis.new + +module Minitest + class Test + def self.em(m) + alias_method "raw_#{m}", m + define_method(m) do + $panic = nil + e = nil + EM.run do + Fiber.new { + begin + send("raw_#{m}") + rescue + e = $! + ensure + EM.stop + end + }.resume + end + raise e if e + end + end + end +end