Merge branch 'number-search'

Stephen Paul Weber created

* number-search:
  Do not direct back to website to pick new number
  Run rubocop after tests
  Add reference to option to show where the tel is
  Format tel for human reader
  Allow user to search for numbers over XMPP
  s/WebRegisterManager/TelSelections

Change summary

.rubocop.yml                      |   7 +
Rakefile                          |   2 
forms/tn_list.rb                  |  14 ++
forms/tn_search.rb                |  15 ++
lib/form_template.rb              |  64 ++++++++++
lib/registration.rb               |  22 ++-
lib/tel_selections.rb             | 198 +++++++++++++++++++++++++++++++++
lib/web_register_manager.rb       |  39 ------
sgx_jmp.rb                        |   8 
test/test_form_template.rb        |  65 ++++++++++
test/test_helper.rb               |  22 +++
test/test_registration.rb         |  36 +++--
test/test_tel_selections.rb       | 131 +++++++++++++++++++++
test/test_web_register_manager.rb |  24 ----
14 files changed, 555 insertions(+), 92 deletions(-)

Detailed changes

.rubocop.yml 🔗

@@ -54,6 +54,9 @@ Style/DoubleNegation:
 Style/PerlBackrefs:
   Enabled: false
 
+Style/SpecialGlobalVars:
+  EnforcedStyle: use_perl_names
+
 Style/RegexpLiteral:
   EnforcedStyle: slashes
   AllowInnerSlashes: true
@@ -82,5 +85,9 @@ Style/FormatString:
 Style/FormatStringToken:
   EnforcedStyle: unannotated
 
+Style/FrozenStringLiteralComment:
+  Exclude:
+    - forms/*
+
 Naming/AccessorMethodName:
   Enabled: false

Rakefile 🔗

@@ -17,7 +17,7 @@ end
 RuboCop::RakeTask.new(:lint)
 
 task :entr do
-	sh "sh", "-c", "git ls-files | entr -s 'rubocop && rake test'"
+	sh "sh", "-c", "git ls-files | entr -s 'rake test && rubocop'"
 end
 
 task default: :test

forms/tn_list.rb 🔗

@@ -0,0 +1,14 @@
+form!
+title "Choose Telephone Number"
+instructions "Please choose one of the following numbers"
+field(
+	var: "tel",
+	required: true,
+	type: "list-single",
+	label: "Telephone Number",
+	options: @tns.map(&:option)
+)
+
+xml.set(xmlns: "http://jabber.org/protocol/rsm") do |xml|
+	xml.count @tns.length.to_s
+end

forms/tn_search.rb 🔗

@@ -0,0 +1,15 @@
+form!
+title "Search Telephone Numbers"
+instructions @error if @error
+field(
+	var: "q",
+	required: true,
+	type: "text-single",
+	label: "Search Telephone Numbers",
+	description:
+		"Enter one of: Area code; six or seven digit " \
+		"number prefix; zip code; city, state/province; " \
+		"or indicate a vanity pattern with ~"
+)
+
+xml.set(xmlns: "http://jabber.org/protocol/rsm")

lib/form_template.rb 🔗

@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+require "blather"
+
+class FormTemplate
+	def initialize(template, filename="template", **kwargs)
+		@args = kwargs
+		@template = template
+		@filename = filename
+		freeze
+	end
+
+	def self.render(path, **kwargs)
+		full_path = File.dirname(__dir__) + "/forms/#{path}.rb"
+		new(File.read(full_path), full_path, **kwargs).render
+	end
+
+	def render(**kwargs)
+		one = OneRender.new(**@args.merge(kwargs))
+		one.instance_eval(@template, @filename)
+		one.form
+	end
+
+	class OneRender
+		def initialize(**kwargs)
+			kwargs.each do |k, v|
+				instance_variable_set("@#{k}", v)
+			end
+			@__form = Blather::Stanza::X.new
+			@__builder = Nokogiri::XML::Builder.with(@__form)
+		end
+
+		def form!
+			@__type_set = true
+			@__form.type = :form
+		end
+
+		def result!
+			@__type_set = true
+			@__form.type = :result
+		end
+
+		def title(s)
+			@__form.title = s
+		end
+
+		def instructions(s)
+			@__form.instructions = s
+		end
+
+		def field(**kwargs)
+			@__form.fields = @__form.fields + [kwargs]
+		end
+
+		def xml
+			@__builder
+		end
+
+		def form
+			raise "Type never set" unless @__type_set
+			@__form
+		end
+	end
+end

lib/registration.rb 🔗

@@ -9,15 +9,15 @@ require_relative "./command"
 require_relative "./bandwidth_tn_order"
 require_relative "./em"
 require_relative "./oob"
-require_relative "./web_register_manager"
+require_relative "./tel_selections"
 
 class Registration
-	def self.for(customer, web_register_manager)
+	def self.for(customer, tel_selections)
 		customer.registered?.then do |registered|
 			if registered
 				Registered.new(registered.phone)
 			else
-				web_register_manager[customer.jid].then(&:choose_tel).then do |tel|
+				tel_selections[customer.jid].then(&:choose_tel).then do |tel|
 					Activation.for(customer, tel)
 				end
 			end
@@ -425,18 +425,20 @@ class Registration
 		def write
 			BandwidthTNOrder.create(@tel).then(&:poll).then(
 				->(_) { customer_active_tel_purchased },
-				lambda do |_|
-					Command.finish(
-						"The JMP number #{@tel} is no longer available, " \
-						"please visit https://jmp.chat and choose another.",
-						type: :error
-					)
-				end
+				->(_) { number_purchase_error }
 			)
 		end
 
 	protected
 
+		def number_purchase_error
+			TEL_SELECTIONS.delete(@customer.jid).then {
+				TelSelections::ChooseTel.new.choose_tel(
+					error: "The JMP number #{@tel} is no longer available."
+				)
+			}.then { |tel| Finish.new(@customer, tel).write }
+		end
+
 		def cheogram_sip_addr
 			"sip:#{ERB::Util.url_encode(@customer.jid)}@sip.cheogram.com"
 		end

lib/tel_selections.rb 🔗

@@ -0,0 +1,198 @@
+# frozen_string_literal: true
+
+require "ruby-bandwidth-iris"
+Faraday.default_adapter = :em_synchrony
+
+require_relative "form_template"
+
+class TelSelections
+	THIRTY_DAYS = 60 * 60 * 24 * 30
+
+	def initialize(redis: REDIS)
+		@redis = redis
+	end
+
+	def set(jid, tel)
+		@redis.setex("pending_tel_for-#{jid}", THIRTY_DAYS, tel)
+	end
+
+	def delete(jid)
+		@redis.del("pending_tel_for-#{jid}")
+	end
+
+	def [](jid)
+		@redis.get("pending_tel_for-#{jid}").then do |tel|
+			tel ? HaveTel.new(tel) : ChooseTel.new
+		end
+	end
+
+	class HaveTel
+		def initialize(tel)
+			@tel = tel
+		end
+
+		def choose_tel
+			EMPromise.resolve(@tel)
+		end
+	end
+
+	class ChooseTel
+		def choose_tel(error: nil)
+			Command.reply { |reply|
+				reply.allowed_actions = [:next]
+				reply.command << FormTemplate.render("tn_search", error: error)
+			}.then { |iq| choose_from_list(AvailableNumber.for(iq.form).tns) }
+		end
+
+		def choose_from_list(tns)
+			if tns.empty?
+				choose_tel(error: "No numbers found, try another search.")
+			else
+				Command.reply { |reply|
+					reply.allowed_actions = [:next]
+					reply.command << FormTemplate.render("tn_list", tns: tns)
+				}.then { |iq| iq.form.field("tel").value.to_s.strip }
+			end
+		end
+
+		class AvailableNumber
+			def self.for(form)
+				new(
+					Q
+					.for(form.field("q").value.to_s.strip).iris_query
+					.merge(enableTNDetail: true)
+					.merge(Quantity.for(form).iris_query)
+				)
+			end
+
+			def initialize(iris_query)
+				@iris_query = iris_query
+			end
+
+			def tns
+				Command.log.debug("BandwidthIris::AvailableNumber.list", @iris_query)
+				BandwidthIris::AvailableNumber.list(@iris_query).map(&Tn.method(:new))
+			end
+
+			class Quantity
+				def self.for(form)
+					rsm_max = form.find(
+						"ns:set/ns:max",
+						ns: "http://jabber.org/protocol/rsm"
+					).first
+					if rsm_max
+						new(rsm_max.content.to_i)
+					else
+						Default.new
+					end
+				end
+
+				def initialize(quantity)
+					@quantity = quantity
+				end
+
+				def iris_query
+					{ quantity: @quantity }
+				end
+
+				# NOTE: Gajim sends back the whole list on submit, so big
+				# lists can cause issues
+				class Default
+					def iris_query
+						{ quantity: 10 }
+					end
+				end
+			end
+		end
+
+		class Tn
+			attr_reader :tel
+
+			def initialize(full_number:, city:, state:, **)
+				@tel = "+1#{full_number}"
+				@locality = city
+				@region = state
+			end
+
+			def formatted_tel
+				@tel =~ /\A\+1(\d{3})(\d{3})(\d+)\Z/
+				"(#{$1}) #{$2}-#{$3}"
+			end
+
+			def option
+				op = Blather::Stanza::X::Field::Option.new(value: tel, label: to_s)
+				op << reference
+				op
+			end
+
+			def reference
+				Nokogiri::XML::Builder.new { |xml|
+					xml.reference(
+						xmlns: "urn:xmpp:reference:0",
+						begin: 0,
+						end: formatted_tel.length - 1,
+						type: "data",
+						uri: "tel:#{tel}"
+					)
+				}.doc.root
+			end
+
+			def to_s
+				"#{formatted_tel} (#{@locality}, #{@region})"
+			end
+		end
+
+		class Q
+			def self.register(regex, &block)
+				@queries ||= []
+				@queries << [regex, block]
+			end
+
+			def self.for(q)
+				@queries.each do |(regex, block)|
+					match_data = (q =~ regex)
+					return block.call($1 || $&, *$~.to_a[2..-1]) if match_data
+				end
+
+				raise "Format not recognized: #{q}"
+			end
+
+			def initialize(q)
+				@q = q
+			end
+
+			{
+				areaCode: [:AreaCode, /\A[2-9][0-9]{2}\Z/],
+				npaNxx: [:NpaNxx, /\A(?:[2-9][0-9]{2}){2}\Z/],
+				npaNxxx: [:NpaNxxx, /\A(?:[2-9][0-9]{2}){2}[0-9]\Z/],
+				zip: [:PostalCode, /\A\d{5}(?:-\d{4})?\Z/],
+				localVanity: [:LocalVanity, /\A~(.+)\Z/]
+			}.each do |k, args|
+				klass = const_set(
+					args[0],
+					Class.new(Q) do
+						define_method(:iris_query) do
+							{ k => @q }
+						end
+					end
+				)
+
+				args[1..-1].each do |regex|
+					register(regex) { |q| klass.new(q) }
+				end
+			end
+
+			class CityState
+				Q.register(/\A([^,]+)\s*,\s*([A-Z]{2})\Z/, &method(:new))
+				def initialize(city, state)
+					@city = city
+					@state = state
+				end
+
+				def iris_query
+					{ city: @city, state: @state }
+				end
+			end
+		end
+	end
+end

lib/web_register_manager.rb 🔗

@@ -1,39 +0,0 @@
-# frozen_string_literal: true
-
-class WebRegisterManager
-	THIRTY_DAYS = 60 * 60 * 24 * 30
-
-	def initialize(redis: REDIS)
-		@redis = redis
-	end
-
-	def set(jid, tel)
-		@redis.setex("pending_tel_for-#{jid}", THIRTY_DAYS, tel)
-	end
-
-	def [](jid)
-		@redis.get("pending_tel_for-#{jid}").then do |tel|
-			tel ? HaveTel.new(tel) : ChooseTel.new
-		end
-	end
-
-	class HaveTel
-		def initialize(tel)
-			@tel = tel
-		end
-
-		def choose_tel
-			EMPromise.resolve(@tel)
-		end
-	end
-
-	class ChooseTel
-		def choose_tel
-			Command.finish(
-				"You have not chosen a phone number yet, please return to " \
-				"https://jmp.chat and choose one now.",
-				type: :error
-			)
-		end
-	end
-end

sgx_jmp.rb 🔗

@@ -77,7 +77,7 @@ require_relative "lib/low_balance"
 require_relative "lib/payment_methods"
 require_relative "lib/registration"
 require_relative "lib/transaction"
-require_relative "lib/web_register_manager"
+require_relative "lib/tel_selections"
 require_relative "lib/session_manager"
 require_relative "lib/statsd"
 
@@ -169,7 +169,7 @@ when_ready do
 	LOG.info "Ready"
 	BLATHER = self
 	REDIS = EM::Hiredis.connect
-	WEB_REGISTER_MANAGER = WebRegisterManager.new
+	TEL_SELECTIONS = TelSelections.new
 	BTC_SELL_PRICES = BTCSellPrices.new(REDIS, CONFIG[:oxr_app_id])
 	DB = PG::EM::ConnectionPool.new(dbname: "jmp") do |conn|
 		conn.type_map_for_results = PG::BasicTypeMapForResults.new(conn)
@@ -392,7 +392,7 @@ Command.new(
 		Command.execution.customer_repo.create(Command.execution.iq.from.stripped)
 	}.then { |customer|
 		Sentry.add_breadcrumb(Sentry::Breadcrumb.new(message: "Registration.for"))
-		Registration.for(customer, WEB_REGISTER_MANAGER).then(&:write)
+		Registration.for(customer, TEL_SELECTIONS).then(&:write)
 	}.then {
 		StatsD.increment("registration.completed")
 	}.catch_only(Command::Execution::FinalStanza) do |e|
@@ -547,7 +547,7 @@ command :execute?, node: "web-register", sessionid: nil do |iq|
 				cmd.form.fields = [var: "to", value: jid]
 				cmd.form.type = "submit"
 			}).then { |result|
-				WEB_REGISTER_MANAGER.set(result.form.field("from")&.value.to_s.strip, tel)
+				TEL_SELECTIONS.set(result.form.field("from")&.value.to_s.strip, tel)
 			}.then {
 				BLATHER << iq.reply.tap { |reply| reply.status = :completed }
 			}.catch { |e| panic(e, sentry_hub) }

test/test_form_template.rb 🔗

@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require "form_template"
+
+class FormTemplateTest < Minitest::Test
+	def test_form_one_field
+		template = FormTemplate.new(<<~TEMPLATE)
+			form!
+			title "TITLE"
+			instructions "INSTRUCTIONS"
+			field(var: "thevar", label: "thelabel")
+		TEMPLATE
+		form = template.render
+		assert_equal :form, form.type
+		assert_equal "TITLE", form.title
+		assert_equal "INSTRUCTIONS", form.instructions
+		assert_equal 1, form.fields.length
+		assert_equal "thevar", form.fields[0].var
+		assert_equal "thelabel", form.fields[0].label
+	end
+
+	def test_form_two_fields
+		template = FormTemplate.new(<<~TEMPLATE)
+			form!
+			field(var: "thevar", label: "thelabel")
+			field(var: "thevar2", label: "thelabel2")
+		TEMPLATE
+		form = template.render
+		assert_equal 2, form.fields.length
+		assert_equal "thevar", form.fields[0].var
+		assert_equal "thelabel", form.fields[0].label
+		assert_equal "thevar2", form.fields[1].var
+		assert_equal "thelabel2", form.fields[1].label
+	end
+
+	def test_result_no_fields
+		template = FormTemplate.new(<<~TEMPLATE)
+			result!
+			title "TITLE"
+			instructions "INSTRUCTIONS"
+		TEMPLATE
+		form = template.render
+		assert_equal :result, form.type
+		assert_equal "TITLE", form.title
+		assert_equal "INSTRUCTIONS", form.instructions
+	end
+
+	def test_no_type
+		template = FormTemplate.new(<<~TEMPLATE)
+			title "TITLE"
+			instructions "INSTRUCTIONS"
+		TEMPLATE
+		assert_raises { template.render }
+	end
+
+	def test_custom_xml
+		template = FormTemplate.new(<<~TEMPLATE)
+			form!
+			xml.whoever @arg
+		TEMPLATE
+		form = template.render(arg: "abc")
+		assert_equal "abc", form.at("whoever").content
+	end
+end

test/test_helper.rb 🔗

@@ -34,6 +34,7 @@ rescue LoadError
 end
 
 require "backend_sgx"
+require "tel_selections"
 
 $VERBOSE = nil
 Sentry.init
@@ -132,6 +133,27 @@ class PromiseMock < Minitest::Mock
 	end
 end
 
+class FakeTelSelections
+	def initialize
+		@selections = {}
+	end
+
+	def set(jid, tel)
+		@selections[jid] = EMPromise.resolve(TelSelections::HaveTel.new(tel))
+	end
+
+	def delete(jid)
+		@selections.delete(jid)
+		EMPromise.resolve("OK")
+	end
+
+	def [](jid)
+		@selections.fetch(jid) do
+			TelSelections::ChooseTel.new
+		end
+	end
+end
+
 class FakeRedis
 	def initialize(values={})
 		@values = values

test/test_registration.rb 🔗

@@ -35,7 +35,7 @@ class RegistrationTest < Minitest::Test
 	em :test_for_registered
 
 	def test_for_activated
-		web_manager = WebRegisterManager.new(redis: FakeRedis.new)
+		web_manager = TelSelections.new(redis: FakeRedis.new)
 		web_manager.set("test@example.net", "+15555550000")
 		result = execute_command do
 			sgx = OpenStruct.new(registered?: EMPromise.resolve(nil))
@@ -54,7 +54,7 @@ class RegistrationTest < Minitest::Test
 
 	def test_for_not_activated_with_customer_id
 		sgx = OpenStruct.new(registered?: EMPromise.resolve(nil))
-		web_manager = WebRegisterManager.new(redis: FakeRedis.new)
+		web_manager = TelSelections.new(redis: FakeRedis.new)
 		web_manager.set("test@example.net", "+15555550000")
 		iq = Blather::Stanza::Iq::Command.new
 		iq.from = "test@example.com"
@@ -514,6 +514,8 @@ class RegistrationTest < Minitest::Test
 	end
 
 	class FinishTest < Minitest::Test
+		Command::COMMAND_MANAGER = Minitest::Mock.new
+		Registration::Finish::TEL_SELECTIONS = FakeTelSelections.new
 		Registration::Finish::REDIS = Minitest::Mock.new
 		BackendSgx::REDIS = Minitest::Mock.new
 
@@ -628,23 +630,29 @@ class RegistrationTest < Minitest::Test
 					<OrderStatus>FAILED</OrderStatus>
 				</OrderResponse>
 			RESPONSE
-			blather = Minitest::Mock.new
-			blather.expect(
-				:<<,
-				nil,
-				[Matching.new do |reply|
-					assert_equal :completed, reply.status
-					assert_equal :error, reply.note_type
+
+			Command::COMMAND_MANAGER.expect(
+				:write,
+				EMPromise.reject(:test_result),
+				[Matching.new do |iq|
+					assert_equal :form, iq.form.type
 					assert_equal(
-						"The JMP number +15555550000 is no longer available, " \
-						"please visit https://jmp.chat and choose another.",
-						reply.note.content
+						"The JMP number +15555550000 is no longer available.",
+						iq.form.instructions
 					)
 				end]
 			)
-			execute_command(blather: blather) { @finish.write }
+
+			assert_equal(
+				:test_result,
+				execute_command { @finish.write.catch { |e| e } }
+			)
+			assert_mock Command::COMMAND_MANAGER
+			assert_instance_of(
+				TelSelections::ChooseTel,
+				Registration::Finish::TEL_SELECTIONS["test@example.com"]
+			)
 			assert_requested create_order
-			assert_mock blather
 		end
 		em :test_write_tn_fail
 	end

test/test_tel_selections.rb 🔗

@@ -0,0 +1,131 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require "tel_selections"
+
+class TelSelectionsTest < Minitest::Test
+	def setup
+		@manager = TelSelections.new(redis: FakeRedis.new)
+	end
+
+	def test_set_get
+		assert_kind_of TelSelections::ChooseTel, @manager["jid@example.com"].sync
+		@manager.set("jid@example.com", "+15555550000").sync
+		assert_kind_of TelSelections::HaveTel, @manager["jid@example.com"].sync
+	end
+	em :test_set_get
+
+	def test_choose_tel_have_tel
+		jid = "jid@example.com"
+		@manager.set(jid, "+15555550000").sync
+		assert_equal "+15555550000", @manager[jid].then(&:choose_tel).sync
+	end
+	em :test_choose_tel_have_tel
+
+	class AvailableNumberTest < Minitest::Test
+		def test_for_no_rsm
+			form = Blather::Stanza::X.new
+			form.fields = [{ var: "q", value: "226" }]
+			iris_query =
+				TelSelections::ChooseTel::AvailableNumber
+				.for(form)
+				.instance_variable_get(:@iris_query)
+			assert_equal(
+				{ areaCode: "226", enableTNDetail: true, quantity: 10 },
+				iris_query
+			)
+		end
+
+		def test_for_rsm
+			form = Blather::Stanza::X.new
+			form.fields = [{ var: "q", value: "226" }]
+			Nokogiri::XML::Builder.with(form) do
+				set(xmlns: "http://jabber.org/protocol/rsm") do
+					max 500
+				end
+			end
+			iris_query =
+				TelSelections::ChooseTel::AvailableNumber
+				.for(form)
+				.instance_variable_get(:@iris_query)
+			assert_equal(
+				{ areaCode: "226", enableTNDetail: true, quantity: 500 },
+				iris_query
+			)
+		end
+	end
+
+	class TnTest < Minitest::Test
+		def setup
+			@tn = TelSelections::ChooseTel::Tn.new(
+				full_number: "5551234567",
+				city: "Toronto",
+				state: "ON",
+				garbage: "stuff"
+			)
+		end
+
+		def test_to_s
+			assert_equal "(555) 123-4567 (Toronto, ON)", @tn.to_s
+		end
+
+		def test_tel
+			assert_equal "+15551234567", @tn.tel
+		end
+
+		def test_option
+			assert_equal(
+				Blather::Stanza::X::Field::Option.new(
+					label: "(555) 123-4567 (Toronto, ON)",
+					value: "+15551234567"
+				),
+				@tn.option
+			)
+		end
+
+		def test_option_reference
+			ref = @tn.option.find("ns:reference", ns: "urn:xmpp:reference:0").first
+			assert_equal(
+				@tn.formatted_tel,
+				@tn.option.label[ref["begin"].to_i..ref["end"].to_i]
+			)
+			assert_equal "tel:+15551234567", ref["uri"]
+		end
+	end
+
+	class QTest < Minitest::Test
+		def test_for_area_code
+			q = TelSelections::ChooseTel::Q.for("226")
+			assert_equal({ areaCode: "226" }, q.iris_query)
+		end
+
+		def test_for_npanxx
+			q = TelSelections::ChooseTel::Q.for("226666")
+			assert_equal({ npaNxx: "226666" }, q.iris_query)
+		end
+
+		def test_for_npanxxx
+			q = TelSelections::ChooseTel::Q.for("2266667")
+			assert_equal({ npaNxxx: "2266667" }, q.iris_query)
+		end
+
+		def test_for_zip
+			q = TelSelections::ChooseTel::Q.for("90210")
+			assert_equal({ zip: "90210" }, q.iris_query)
+		end
+
+		def test_for_localvanity
+			q = TelSelections::ChooseTel::Q.for("~mboa")
+			assert_equal({ localVanity: "mboa" }, q.iris_query)
+		end
+
+		def test_for_citystate
+			q = TelSelections::ChooseTel::Q.for("Toronto, ON")
+			assert_equal({ city: "Toronto", state: "ON" }, q.iris_query)
+		end
+
+		def test_for_garbage
+			assert_raises { TelSelections::ChooseTel::Q.for("garbage") }
+		end
+	end
+end

test/test_web_register_manager.rb 🔗

@@ -1,24 +0,0 @@
-# frozen_string_literal: true
-
-require "test_helper"
-require "web_register_manager"
-
-class WebRegisterManagerTest < Minitest::Test
-	def setup
-		@manager = WebRegisterManager.new(redis: FakeRedis.new)
-	end
-
-	def test_set_get
-		assert_kind_of WebRegisterManager::ChooseTel, @manager["jid@example.com"].sync
-		@manager.set("jid@example.com", "+15555550000").sync
-		assert_kind_of WebRegisterManager::HaveTel, @manager["jid@example.com"].sync
-	end
-	em :test_set_get
-
-	def test_choose_tel_have_tel
-		jid = "jid@example.com"
-		@manager.set(jid, "+15555550000").sync
-		assert_equal "+15555550000", @manager[jid].then(&:choose_tel).sync
-	end
-	em :test_choose_tel_have_tel
-end