Merge branch 'ibr-refactor' into 'master'

Stephen Paul Weber created

Ibr refactor

See merge request soprani.ca/sgx-bwmsgsv2!14

Change summary

sgx-bwmsgsv2.rb        | 105 +++++++++++--------------------------------
test/test_component.rb |  83 ++++++++++++++++++++++++++++++++++
test/test_helper.rb    |  13 +++++
3 files changed, 123 insertions(+), 78 deletions(-)

Detailed changes

sgx-bwmsgsv2.rb 🔗

@@ -524,58 +524,28 @@ module SGXbwmsgsv2
 			}
 	end
 
-	def self.creds_from_registration_query(qn)
-		xn = qn.children.find { |v| v.element_name == "x" }
-
-		if xn
-			xn.children.each_with_object({}) do |field, h|
-				next if field.element_name != "field"
-
-				val = field.children.find { |v|
-					v.element_name == "value"
-				}
-
-				case field['var']
-				when 'nick'
-					h[:user_id] = val.text
-				when 'username'
-					h[:api_token] = val.text
-				when 'password'
-					h[:api_secret] = val.text
-				when 'phone'
-					h[:phone_num] = val.text
-				else
-					# TODO: error
-					puts "?: #{field['var']}"
-				end
-			end
+	def self.creds_from_registration_query(i)
+		if i.query.find_first("./ns:x", ns: "jabber:x:data")
+			[
+				i.form.field("nick")&.value,
+				i.form.field("username")&.value,
+				i.form.field("password")&.value,
+				i.form.field("phone")&.value
+			]
 		else
-			qn.children.each_with_object({}) do |field, h|
-				case field.element_name
-				when "nick"
-					h[:user_id] = field.text
-				when "username"
-					h[:api_token] = field.text
-				when "password"
-					h[:api_secret] = field.text
-				when "phone"
-					h[:phone_num] = field.text
-				end
-			end
-		end.values_at(:user_id, :api_token, :api_secret, :phone_num)
+			[i.nick, i.username, i.password, i.phone]
+		end
 	end
 
-	def self.process_registration(i, qn)
-		EMPromise.resolve(
-			qn.children.find { |v| v.element_name == "remove" }
-		).then { |rn|
-			if rn
+	def self.process_registration(i)
+		EMPromise.resolve(nil).then {
+			if i.remove?
 				@registration_repo.delete(i.from).then do
 					write_to_stream i.reply
 					EMPromise.reject(:done)
 				end
 			else
-				creds_from_registration_query(qn)
+				creds_from_registration_query(i)
 			end
 		}.then { |user_id, api_token, api_secret, phone_num|
 			if phone_num && phone_num[0] == '+'
@@ -620,22 +590,10 @@ module SGXbwmsgsv2
 	end
 
 	def self.registration_form(orig, existing_number=nil)
-		msg = Nokogiri::XML::Node.new 'query', orig.document
-		msg['xmlns'] = 'jabber:iq:register'
-
-		if existing_number
-			msg.add_child(
-				Nokogiri::XML::Node.new(
-					'registered', msg.document
-				)
-			)
-		end
+		orig.registered = !!existing_number
 
 		# TODO: update "User Id" x2 below (to "accountId"?), and others?
-		n1 = Nokogiri::XML::Node.new(
-			'instructions', msg.document
-		)
-		n1.content = "Enter the information from your Account "\
+		orig.instructions = "Enter the information from your Account "\
 			"page as well as the Phone Number\nin your "\
 			"account you want to use (ie. '+12345678901')"\
 			".\nUser Id is nick, API Token is username, "\
@@ -644,18 +602,12 @@ module SGXbwmsgsv2
 			"https://gitlab.com/soprani.ca/sgx-bwmsgsv2 ."\
 			"\nCopyright (C) 2017-2020  Denver Gingerich "\
 			"and others, licensed under AGPLv3+."
-		n2 = Nokogiri::XML::Node.new 'nick', msg.document
-		n3 = Nokogiri::XML::Node.new 'username', msg.document
-		n4 = Nokogiri::XML::Node.new 'password', msg.document
-		n5 = Nokogiri::XML::Node.new 'phone', msg.document
-		n5.content = existing_number.to_s
-		msg.add_child(n1)
-		msg.add_child(n2)
-		msg.add_child(n3)
-		msg.add_child(n4)
-		msg.add_child(n5)
-
-		x = Blather::Stanza::X.new :form, [
+		orig.nick = ""
+		orig.username = ""
+		orig.password = ""
+		orig.phone = existing_number.to_s
+
+		orig.form.fields = [
 			{
 				required: true, type: :"text-single",
 				label: 'User Id', var: 'nick'
@@ -674,28 +626,25 @@ module SGXbwmsgsv2
 				value: existing_number.to_s
 			}
 		]
-		x.title = 'Register for '\
+		orig.form.title = 'Register for '\
 			'Soprani.ca Gateway to XMPP - Bandwidth API V2'
-		x.instructions = "Enter the details from your Account "\
+		orig.form.instructions = "Enter the details from your Account "\
 			"page as well as the Phone Number\nin your "\
 			"account you want to use (ie. '+12345678901')"\
 			".\n\nThe source code for this gateway is at "\
 			"https://gitlab.com/soprani.ca/sgx-bwmsgsv2 ."\
 			"\nCopyright (C) 2017-2020  Denver Gingerich "\
 			"and others, licensed under AGPLv3+."
-		msg.add_child(x)
-
-		orig.add_child(msg)
 
-		return orig
+		orig
 	end
 
-	iq '/iq/ns:query', ns: 'jabber:iq:register' do |i, qn|
+	ibr do |i|
 		puts "IQ: #{i.inspect}"
 
 		case i.type
 		when :set
-			process_registration(i, qn)
+			process_registration(i)
 		when :get
 			bare_jid = i.from.stripped
 			@registration_repo.find(bare_jid).then { |creds|

test/test_component.rb 🔗

@@ -16,6 +16,7 @@ class ComponentTest < Minitest::Test
 			@written << s
 		end
 
+		REDIS.reset!
 		REDIS.set("catapult_cred-test@example.com", [
 			'account', 'user', 'password', '+15550000000'
 		])
@@ -265,4 +266,86 @@ class ComponentTest < Minitest::Test
 		)
 	end
 	em :test_ibr_conflict
+
+	def test_ibr_remove
+		iq = Blather::Stanza::Iq::IBR.new(:set, "component")
+		iq.from = "test@example.com"
+		iq.remove!
+		process_stanza(iq)
+
+		refute REDIS.get("catapult_cred-test@example.com").sync
+
+		assert_equal 1, written.length
+
+		stanza = Blather::XMPPNode.parse(written.first.to_xml)
+		assert stanza.result?
+	end
+	em :test_ibr_remove
+
+	def test_ibr_form
+		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 = "formuser@example.com"
+		form = Blather::Stanza::X.find_or_create(iq.query)
+		form.fields = [
+			{
+				var: "nick",
+				value: "acct"
+			},
+			{
+				var: "username",
+				value: "user"
+			},
+			{
+				var: "password",
+				value: "pw"
+			},
+			{
+				var: "phone",
+				value: "+15551234567"
+			}
+		]
+		process_stanza(iq)
+
+		assert_equal(
+			["acct", "user", "pw", "+15551234567"],
+			REDIS.get("catapult_cred-formuser@example.com").sync
+		)
+
+		assert_equal(
+			"formuser@example.com",
+			REDIS.get("catapult_jid-+15551234567").sync
+		)
+
+		assert_equal 1, written.length
+		stanza = Blather::XMPPNode.parse(written.first.to_xml)
+		assert stanza.result?
+	end
+	em :test_ibr_form
+
+	def test_ibr_get_form_registered
+		iq = Blather::Stanza::Iq::IBR.new(:get, "component")
+		iq.from = "test@example.com"
+		process_stanza(iq)
+
+		assert_equal 1, written.length
+		stanza = Blather::XMPPNode.parse(written.first.to_xml)
+		assert stanza.result?
+		assert stanza.registered?
+		assert_equal(
+			["nick", "username", "password", "phone"],
+			stanza.form.fields.map(&:var)
+		)
+		assert stanza.instructions
+		assert stanza.nick
+		assert stanza.username
+		assert stanza.password
+		assert stanza.phone
+		refute stanza.email
+	end
+	em :test_ibr_get_form_registered
 end

test/test_helper.rb 🔗

@@ -38,6 +38,10 @@ class FakeRedis
 		@values = values
 	end
 
+	def reset!(values={})
+		@values = values
+	end
+
 	def set(key, value, *)
 		@values[key] = value
 		EMPromise.resolve("OK")
@@ -47,6 +51,10 @@ class FakeRedis
 		set(key, value)
 	end
 
+	def del(*keys)
+		keys.each { |key| @values.delete(key) }
+	end
+
 	def mget(*keys)
 		EMPromise.all(keys.map(&method(:get)))
 	end
@@ -109,6 +117,11 @@ class FakeRedis
 	def lrange(key, sindex, eindex)
 		get(key).then { |v| v ? v[sindex..eindex] : [] }
 	end
+
+	def rpush(key, *values)
+		@values[key] ||= []
+		values.each { |v| @values[key].push(v) }
+	end
 end
 
 REDIS = FakeRedis.new