test_porting_step.rb

  1# frozen_string_literal: true
  2
  3require "date"
  4require "ostruct"
  5require "test_helper"
  6
  7require "customer_info"
  8require "form_template"
  9require "form_to_h"
 10require "porting_step_repo"
 11require "port_repo"
 12
 13MINS = 1.0 / (24 * 60)
 14
 15class BlatherNotifyMock < Minitest::Mock
 16	def initialize
 17		super
 18		@exes = []
 19	end
 20
 21	def expect_execution(server, node, *args)
 22		exe = Execution.new(args)
 23		expect(
 24			:command_execution,
 25			exe,
 26			[server, node]
 27		)
 28		@exes << exe
 29	end
 30
 31	def verify
 32		super
 33		@exes.each(&:verify)
 34	end
 35
 36	class Execution < Minitest::Mock
 37		def initialize(args)
 38			super()
 39			args.each_slice(2) do |(submission, result)|
 40				expect(
 41					:fetch_and_submit,
 42					to_promise(result),
 43					**submission
 44				)
 45			end
 46		end
 47
 48		using FormToH
 49
 50		def to_promise(result)
 51			if result.is_a?(Exception)
 52				EMPromise.reject(result)
 53			else
 54				EMPromise.resolve(to_response(result))
 55			end
 56		end
 57
 58		def to_response(form)
 59			OpenStruct.new(form.to_h)
 60		end
 61	end
 62end
 63
 64def info(tel)
 65	CustomerInfo.new(
 66		plan_info: PlanInfo::NoPlan.new,
 67		tel: tel,
 68		balance: 0.to_d,
 69		cnam: nil
 70	)
 71end
 72
 73def admin_info(customer_id, tel)
 74	AdminInfo.new(
 75		jid: Blather::JID.new("#{customer_id}@example.com"),
 76		customer_id: customer_id,
 77		fwd: nil,
 78		info: info(tel),
 79		call_info: "",
 80		trust_level: "",
 81		backend: BackendSgx.new(
 82			jid: Blather::JID.new("testroute"),
 83			from_jid: Blather::JID.new("customer_#{customer_id}@example.com"),
 84			creds: {},
 85			transcription_enabled: false,
 86			registered?: false,
 87			fwd: nil,
 88			ogm_url: nil
 89		)
 90	)
 91end
 92
 93def menu
 94	FormTemplate.render("admin_menu")
 95end
 96
 97class PortingStepTest < Minitest::Test
 98	Port = Struct.new(
 99		:id,
100		:processing_status,
101		:actual_foc_date,
102		:updated_at,
103		:customer_id,
104		:tel,
105		:backend_sgx
106	)
107
108	def test_ignore_submitted_ports
109		redis = Minitest::Mock.new
110		redis.expect(:exists, EMPromise.resolve(0), ["jmp_port_freeze-01"])
111
112		step = PortingStepRepo.new(redis: redis).find(Port.new(
113			"01",
114			"SUBMITTED",
115			nil,
116			DateTime.now - 1 * MINS,
117			"ignored",
118			"9998887777",
119			Blather::JID.new("testroute")
120		)).sync
121
122		assert_kind_of PortingStepRepo::Wait, step
123	end
124	em :test_ignore_submitted_ports
125
126	def test_ignore_recent_foc
127		redis = Minitest::Mock.new
128		redis.expect(:exists, EMPromise.resolve(0), ["jmp_port_freeze-01"])
129
130		step = PortingStepRepo.new(redis: redis).find(Port.new(
131			"01",
132			"FOC",
133			DateTime.now - 5 * MINS,
134			DateTime.now - 1 * MINS,
135			"ignored",
136			"9998887777",
137			Blather::JID.new("testroute")
138		)).sync
139
140		assert_kind_of PortingStepRepo::Wait, step
141	end
142	em :test_ignore_recent_foc
143
144	def test_warn_for_late_foc
145		redis = Minitest::Mock.new
146		redis.expect(:exists, EMPromise.resolve(0), ["jmp_port_freeze-01"])
147
148		step = PortingStepRepo.new(redis: redis).find(Port.new(
149			"01",
150			"FOC",
151			DateTime.now - 25 * MINS,
152			DateTime.now - 1 * MINS,
153			"ignored",
154			"9998887777",
155			Blather::JID.new("testroute")
156		)).sync
157
158		assert_kind_of PortingStepRepo::Alert, step
159		assert_equal :late_foc, step.key
160		assert_kind_of PortingStepRepo::Wait, step.real_step
161	end
162	em :test_warn_for_late_foc
163
164	def test_already_complete
165		redis = Minitest::Mock.new
166		redis.expect(:exists, EMPromise.resolve(0), ["jmp_port_freeze-01"])
167		redis.expect(:exists, 1, ["jmp_port_complete-01"])
168
169		step = PortingStepRepo.new(redis: redis).find(Port.new(
170			"01",
171			"COMPLETE",
172			DateTime.now - 25 * MINS,
173			DateTime.now - 1 * MINS,
174			"completed",
175			"9998887777",
176			Blather::JID.new("testroute")
177		)).sync
178
179		assert_kind_of PortingStepRepo::Done, step
180		assert_mock redis
181	end
182	em :test_already_complete
183
184	def test_change_number
185		redis = Minitest::Mock.new
186		redis.expect(:exists, EMPromise.resolve(0), ["jmp_port_freeze-01"])
187		redis.expect(:exists, "0", ["jmp_port_complete-01"])
188
189		notify = BlatherNotifyMock.new
190		notify.expect_execution(
191			"sgx", "customer info",
192			{ q: "starting" }, admin_info("starting", "+19998881111").form
193		)
194
195		step = PortingStepRepo.new(
196			redis: redis,
197			blather_notify: notify,
198			admin_server: "sgx"
199		).find(Port.new(
200			"01",
201			"COMPLETE",
202			DateTime.now - 25 * MINS,
203			DateTime.now - 1 * MINS,
204			"starting",
205			"9998887777",
206			Blather::JID.new("testroute")
207		)).sync
208
209		assert_kind_of PortingStepRepo::Complete::AdminCommand::WrongNumber, step
210		assert_equal Blather::JID.new("testroute"), step.new_backend
211		assert_mock redis
212		assert_mock notify
213	end
214	em :test_change_number
215
216	def test_change_backend_only
217		redis = Minitest::Mock.new
218		redis.expect(:exists, EMPromise.resolve(0), ["jmp_port_freeze-01"])
219		redis.expect(:exists, "0", ["jmp_port_complete-01"])
220
221		notify = BlatherNotifyMock.new
222		notify.expect_execution(
223			"sgx", "customer info",
224			{ q: "starting" }, admin_info("starting", "+19998887777").form
225		)
226		notify.expect(:command_execution, nil) do |_server, node|
227			if node == "reachability"
228				raise(
229					Minitest::Assertion,
230					"reachability must not be invoked when backend needs to change"
231				)
232			end
233			false
234		end
235
236		step = PortingStepRepo.new(
237			redis: redis,
238			blather_notify: notify,
239			admin_server: "sgx"
240		).find(Port.new(
241			"01",
242			"COMPLETE",
243			DateTime.now - 25 * MINS,
244			DateTime.now - 1 * MINS,
245			"starting",
246			"+19998887777",
247			Blather::JID.new("newroute")
248		)).sync
249
250		assert_kind_of(
251			PortingStepRepo::Complete::AdminCommand::WrongNumber, step
252		)
253		assert_equal Blather::JID.new("newroute"), step.new_backend
254	end
255	em :test_change_backend_only
256
257	def test_change_number_does_not_invoke_reachability
258		redis = Minitest::Mock.new
259		redis.expect(:exists, EMPromise.resolve(0), ["jmp_port_freeze-01"])
260		redis.expect(:exists, "0", ["jmp_port_complete-01"])
261
262		notify = BlatherNotifyMock.new
263		notify.expect_execution(
264			"sgx", "customer info",
265			{ q: "starting" }, admin_info("starting", "+19998881111").form
266		)
267		notify.expect(:command_execution, nil) do |_server, node|
268			if node == "reachability"
269				raise(
270					Minitest::Assertion,
271					"reachability must not be invoked before backend change"
272				)
273			end
274			false
275		end
276
277		step = PortingStepRepo.new(
278			redis: redis,
279			blather_notify: notify,
280			admin_server: "sgx"
281		).find(Port.new(
282			"01",
283			"COMPLETE",
284			DateTime.now - 25 * MINS,
285			DateTime.now - 1 * MINS,
286			"starting",
287			"9998887777",
288			Blather::JID.new("testroute")
289		)).sync
290
291		assert_kind_of(
292			PortingStepRepo::Complete::AdminCommand::WrongNumber, step
293		)
294	end
295	em :test_change_number_does_not_invoke_reachability
296
297	def test_unknown_customer
298		redis = Minitest::Mock.new
299		redis.expect(:exists, EMPromise.resolve(0), ["jmp_port_freeze-01"])
300		redis.expect(:exists, "0", ["jmp_port_complete-01"])
301
302		notify = BlatherNotifyMock.new
303		notify.expect_execution(
304			"sgx", "customer info",
305			{ q: "unknown_customer" }, admin_info("unknown_customer", nil).form
306		)
307
308		step = PortingStepRepo.new(
309			redis: redis,
310			blather_notify: notify,
311			admin_server: "sgx"
312		).find(Port.new(
313			"01",
314			"COMPLETE",
315			DateTime.now - 25 * MINS,
316			DateTime.now - 1 * MINS,
317			"unknown_customer",
318			"9998887777",
319			Blather::JID.new("testroute")
320		)).sync
321
322		assert_kind_of PortingStepRepo::Complete::AdminCommand::WrongNumber, step
323		assert_mock redis
324		assert_mock notify
325	end
326	em :test_unknown_customer
327
328	def test_first_reachability
329		redis = Minitest::Mock.new
330		redis.expect(:exists, EMPromise.resolve(0), ["jmp_port_freeze-01"])
331		redis.expect(:exists, "0", ["jmp_port_complete-01"])
332
333		notify = BlatherNotifyMock.new
334		notify.expect_execution(
335			"sgx", "customer info",
336			{ q: "starting" }, admin_info("starting", "+19998887777").form
337		)
338
339		notify.expect_execution(
340			"sgx", "reachability",
341			{ tel: "+19998887777", type: "voice" },
342			FormTemplate.render("reachability_result", count: 0)
343		)
344
345		step = PortingStepRepo.new(
346			redis: redis,
347			blather_notify: notify,
348			admin_server: "sgx"
349		).find(Port.new(
350			"01",
351			"COMPLETE",
352			DateTime.now - 25 * MINS,
353			DateTime.now - 1 * MINS,
354			"starting",
355			"+19998887777",
356			Blather::JID.new("testroute")
357		)).sync
358
359		assert_kind_of(
360			PortingStepRepo::Complete::AdminCommand::GoodNumber::
361				Reachability::RunTest,
362			step
363		)
364		assert_equal "voice", step.type
365		assert_mock redis
366		assert_mock notify
367	end
368	em :test_first_reachability
369
370	def test_reach_sms_reachability
371		redis = Minitest::Mock.new
372		redis.expect(:exists, EMPromise.resolve(0), ["jmp_port_freeze-01"])
373		redis.expect(:exists, "0", ["jmp_port_complete-01"])
374
375		notify = BlatherNotifyMock.new
376		notify.expect_execution(
377			"sgx", "customer info",
378			{ q: "starting" }, admin_info("starting", "+19998887777").form
379		)
380
381		notify.expect_execution(
382			"sgx", "reachability",
383			{ tel: "+19998887777", type: "voice" },
384			FormTemplate.render("reachability_result", count: 1)
385		)
386
387		notify.expect_execution(
388			"sgx", "reachability",
389			{ tel: "+19998887777", type: "sms" },
390			FormTemplate.render("reachability_result", count: 0)
391		)
392
393		step = PortingStepRepo.new(
394			redis: redis,
395			blather_notify: notify,
396			admin_server: "sgx"
397		).find(Port.new(
398			"01",
399			"COMPLETE",
400			DateTime.now - 25 * MINS,
401			DateTime.now - 1 * MINS,
402			"starting",
403			"+19998887777",
404			Blather::JID.new("testroute")
405		)).sync
406
407		assert_kind_of(
408			PortingStepRepo::Complete::AdminCommand::GoodNumber::
409				Reachability::RunTest,
410			step
411		)
412		assert_equal "sms", step.type
413		assert_mock redis
414		assert_mock notify
415	end
416	em :test_reach_sms_reachability
417
418	def test_all_reachable
419		redis = Minitest::Mock.new
420		redis.expect(:exists, EMPromise.resolve(0), ["jmp_port_freeze-01"])
421		redis.expect(:exists, "0", ["jmp_port_complete-01"])
422
423		notify = BlatherNotifyMock.new
424		notify.expect_execution(
425			"sgx", "customer info",
426			{ q: "starting" }, admin_info("starting", "+19998887777").form
427		)
428
429		notify.expect_execution(
430			"sgx", "reachability",
431			{ tel: "+19998887777", type: "voice" },
432			FormTemplate.render("reachability_result", count: 1)
433		)
434
435		notify.expect_execution(
436			"sgx", "reachability",
437			{ tel: "+19998887777", type: "sms" },
438			FormTemplate.render("reachability_result", count: 1)
439		)
440
441		step = PortingStepRepo.new(
442			redis: redis,
443			blather_notify: notify,
444			admin_server: "sgx"
445		).find(Port.new(
446			"01",
447			"COMPLETE",
448			DateTime.now - 25 * MINS,
449			DateTime.now - 1 * MINS,
450			"starting",
451			"+19998887777",
452			Blather::JID.new("testroute")
453		)).sync
454
455		assert_kind_of(
456			PortingStepRepo::Complete::AdminCommand::GoodNumber::FinishUp,
457			step
458		)
459		assert_mock redis
460		assert_mock notify
461	end
462	em :test_all_reachable
463
464	def test_not_done_in_time
465		redis = Minitest::Mock.new
466		redis.expect(:exists, EMPromise.resolve(0), ["jmp_port_freeze-01"])
467		redis.expect(:exists, "0", ["jmp_port_complete-01"])
468
469		notify = BlatherNotifyMock.new
470		notify.expect_execution(
471			"sgx", "customer info",
472			{ q: "starting" }, admin_info("starting", "+19998887777").form
473		)
474
475		notify.expect_execution(
476			"sgx", "reachability",
477			{ tel: "+19998887777", type: "voice" },
478			FormTemplate.render("reachability_result", count: 0)
479		)
480
481		step = PortingStepRepo.new(
482			redis: redis,
483			blather_notify: notify,
484			admin_server: "sgx"
485		).find(Port.new(
486			"01",
487			"COMPLETE",
488			DateTime.now - 55 * MINS,
489			DateTime.now - 50 * MINS,
490			"starting",
491			"+19998887777",
492			Blather::JID.new("testroute")
493		)).sync
494
495		assert_kind_of PortingStepRepo::Alert, step
496		assert_equal :late_finish, step.key
497		assert_kind_of(
498			PortingStepRepo::Complete::AdminCommand::GoodNumber::
499				Reachability::RunTest,
500			step.real_step
501		)
502		assert_mock redis
503		assert_mock notify
504	end
505	em :test_not_done_in_time
506
507	def test_ignore_frozen_ports
508		# This tests that we ignore ports in various states
509		[
510			Port.new(
511				"01",
512				"SUBMITTED",
513				nil,
514				DateTime.now - 1 * MINS,
515				"ignored",
516				"+19998887777",
517				Blather::JID.new("testroute")
518			),
519			Port.new(
520				"01",
521				"FOC",
522				DateTime.now - 300 * MINS,
523				DateTime.now - 300 * MINS,
524				"ignored",
525				"+19998887777",
526				Blather::JID.new("testroute")
527			),
528			Port.new(
529				"01",
530				"COMPLETED",
531				DateTime.now - 10 * MINS,
532				DateTime.now - 10 * MINS,
533				"ignored",
534				"+19998887777",
535				Blather::JID.new("testroute")
536			),
537			Port.new(
538				"01",
539				"COMPLETED",
540				DateTime.now - 300 * MINS,
541				DateTime.now - 300 * MINS,
542				"ignored",
543				"+19998887777",
544				Blather::JID.new("testroute")
545			)
546		].each do |port|
547			redis = Minitest::Mock.new
548			redis.expect(:exists, EMPromise.resolve(1), ["jmp_port_freeze-01"])
549
550			step = PortingStepRepo.new(redis: redis).find(port).sync
551			assert_kind_of PortingStepRepo::Frozen, step
552		end
553	end
554	em :test_ignore_frozen_ports
555
556	def test_reachability_error_during_find
557		redis = Minitest::Mock.new
558		redis.expect(:exists, EMPromise.resolve(0), ["jmp_port_freeze-01"])
559		redis.expect(:exists, "0", ["jmp_port_complete-01"])
560
561		notify = BlatherNotifyMock.new
562		notify.expect_execution(
563			"sgx", "customer info",
564			{ q: "starting" }, admin_info("starting", "+19998887777").form
565		)
566
567		notify.expect_execution(
568			"sgx", "reachability",
569			{ tel: "+19998887777", type: "voice" },
570			RuntimeError.new("Sender not in whitelist")
571		)
572
573		step = PortingStepRepo.new(
574			redis: redis,
575			blather_notify: notify,
576			admin_server: "sgx"
577		).find(Port.new(
578			"01",
579			"COMPLETE",
580			DateTime.now - 25 * MINS,
581			DateTime.now - 1 * MINS,
582			"starting",
583			"+19998887777",
584			Blather::JID.new("testroute")
585		)).sync
586
587		assert_kind_of PortingStepRepo::Alert, step
588		assert_equal :reachability_failure, step.key
589		assert_nil step.real_step
590		assert_mock redis
591		assert_mock notify
592	end
593	em :test_reachability_error_during_find
594
595	def test_reachability_error_during_run_test
596		redis = Minitest::Mock.new
597		redis.expect(:exists, EMPromise.resolve(0), ["jmp_port_freeze-01"])
598		redis.expect(:exists, "0", ["jmp_port_complete-01"])
599
600		notify = BlatherNotifyMock.new
601		notify.expect_execution(
602			"sgx", "customer info",
603			{ q: "starting" }, admin_info("starting", "+19998887777").form
604		)
605
606		notify.expect_execution(
607			"sgx", "reachability",
608			{ tel: "+19998887777", type: "voice" },
609			FormTemplate.render("reachability_result", count: 0)
610		)
611
612		notify.expect_execution(
613			"sgx", "reachability",
614			{ tel: "+19998887777", type: "voice", reachability_tel: "+15551234567" },
615			RuntimeError.new("Sender not in whitelist")
616		)
617
618		output_mock = Minitest::Mock.new
619		output_mock.expect(
620			:warn,
621			EMPromise.resolve(nil),
622			[
623				"01",
624				:reachability_failure,
625				Matching.new { |m| m =~ /Error checking.*reachability/ }
626			]
627		)
628		output = MockOutputs.new(output_mock)
629
630		port = Port.new(
631			"01",
632			"COMPLETE",
633			DateTime.now - 25 * MINS,
634			DateTime.now - 1 * MINS,
635			"starting",
636			"+19998887777",
637			Blather::JID.new("testroute")
638		)
639
640		step = PortingStepRepo.new(
641			redis: redis,
642			blather_notify: notify,
643			admin_server: "sgx",
644			output: output,
645			testing_tel: "+15551234567"
646		).find(port).sync
647
648		assert_kind_of(
649			PortingStepRepo::Complete::AdminCommand::GoodNumber::
650				Reachability::RunTest,
651			step
652		)
653
654		step.perform_next_step.sync
655
656		assert_mock redis
657		assert_mock notify
658		assert_mock output_mock
659	end
660	em :test_reachability_error_during_run_test
661end