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::Alert, step
323 assert_equal :port_for_unknown_customer, step.key
324 assert_kind_of PortingStepRepo::Complete::NoCustomer, step.real_step
325 assert_mock redis
326 assert_mock notify
327 end
328 em :test_unknown_customer
329
330 def test_first_reachability
331 redis = Minitest::Mock.new
332 redis.expect(:exists, EMPromise.resolve(0), ["jmp_port_freeze-01"])
333 redis.expect(:exists, "0", ["jmp_port_complete-01"])
334
335 notify = BlatherNotifyMock.new
336 notify.expect_execution(
337 "sgx", "customer info",
338 { q: "starting" }, admin_info("starting", "+19998887777").form
339 )
340
341 notify.expect_execution(
342 "sgx", "reachability",
343 { tel: "+19998887777", type: "voice" },
344 FormTemplate.render("reachability_result", count: 0)
345 )
346
347 step = PortingStepRepo.new(
348 redis: redis,
349 blather_notify: notify,
350 admin_server: "sgx"
351 ).find(Port.new(
352 "01",
353 "COMPLETE",
354 DateTime.now - 25 * MINS,
355 DateTime.now - 1 * MINS,
356 "starting",
357 "+19998887777",
358 Blather::JID.new("testroute")
359 )).sync
360
361 assert_kind_of(
362 PortingStepRepo::Complete::AdminCommand::GoodNumber::
363 Reachability::RunTest,
364 step
365 )
366 assert_equal "voice", step.type
367 assert_mock redis
368 assert_mock notify
369 end
370 em :test_first_reachability
371
372 def test_reach_sms_reachability
373 redis = Minitest::Mock.new
374 redis.expect(:exists, EMPromise.resolve(0), ["jmp_port_freeze-01"])
375 redis.expect(:exists, "0", ["jmp_port_complete-01"])
376
377 notify = BlatherNotifyMock.new
378 notify.expect_execution(
379 "sgx", "customer info",
380 { q: "starting" }, admin_info("starting", "+19998887777").form
381 )
382
383 notify.expect_execution(
384 "sgx", "reachability",
385 { tel: "+19998887777", type: "voice" },
386 FormTemplate.render("reachability_result", count: 1)
387 )
388
389 notify.expect_execution(
390 "sgx", "reachability",
391 { tel: "+19998887777", type: "sms" },
392 FormTemplate.render("reachability_result", count: 0)
393 )
394
395 step = PortingStepRepo.new(
396 redis: redis,
397 blather_notify: notify,
398 admin_server: "sgx"
399 ).find(Port.new(
400 "01",
401 "COMPLETE",
402 DateTime.now - 25 * MINS,
403 DateTime.now - 1 * MINS,
404 "starting",
405 "+19998887777",
406 Blather::JID.new("testroute")
407 )).sync
408
409 assert_kind_of(
410 PortingStepRepo::Complete::AdminCommand::GoodNumber::
411 Reachability::RunTest,
412 step
413 )
414 assert_equal "sms", step.type
415 assert_mock redis
416 assert_mock notify
417 end
418 em :test_reach_sms_reachability
419
420 def test_all_reachable
421 redis = Minitest::Mock.new
422 redis.expect(:exists, EMPromise.resolve(0), ["jmp_port_freeze-01"])
423 redis.expect(:exists, "0", ["jmp_port_complete-01"])
424
425 notify = BlatherNotifyMock.new
426 notify.expect_execution(
427 "sgx", "customer info",
428 { q: "starting" }, admin_info("starting", "+19998887777").form
429 )
430
431 notify.expect_execution(
432 "sgx", "reachability",
433 { tel: "+19998887777", type: "voice" },
434 FormTemplate.render("reachability_result", count: 1)
435 )
436
437 notify.expect_execution(
438 "sgx", "reachability",
439 { tel: "+19998887777", type: "sms" },
440 FormTemplate.render("reachability_result", count: 1)
441 )
442
443 step = PortingStepRepo.new(
444 redis: redis,
445 blather_notify: notify,
446 admin_server: "sgx"
447 ).find(Port.new(
448 "01",
449 "COMPLETE",
450 DateTime.now - 25 * MINS,
451 DateTime.now - 1 * MINS,
452 "starting",
453 "+19998887777",
454 Blather::JID.new("testroute")
455 )).sync
456
457 assert_kind_of(
458 PortingStepRepo::Complete::AdminCommand::GoodNumber::FinishUp,
459 step
460 )
461 assert_mock redis
462 assert_mock notify
463 end
464 em :test_all_reachable
465
466 def test_not_done_in_time
467 redis = Minitest::Mock.new
468 redis.expect(:exists, EMPromise.resolve(0), ["jmp_port_freeze-01"])
469 redis.expect(:exists, "0", ["jmp_port_complete-01"])
470
471 notify = BlatherNotifyMock.new
472 notify.expect_execution(
473 "sgx", "customer info",
474 { q: "starting" }, admin_info("starting", "+19998887777").form
475 )
476
477 notify.expect_execution(
478 "sgx", "reachability",
479 { tel: "+19998887777", type: "voice" },
480 FormTemplate.render("reachability_result", count: 0)
481 )
482
483 step = PortingStepRepo.new(
484 redis: redis,
485 blather_notify: notify,
486 admin_server: "sgx"
487 ).find(Port.new(
488 "01",
489 "COMPLETE",
490 DateTime.now - 55 * MINS,
491 DateTime.now - 50 * MINS,
492 "starting",
493 "+19998887777",
494 Blather::JID.new("testroute")
495 )).sync
496
497 assert_kind_of PortingStepRepo::Alert, step
498 assert_equal :late_finish, step.key
499 assert_kind_of(
500 PortingStepRepo::Complete::AdminCommand::GoodNumber::
501 Reachability::RunTest,
502 step.real_step
503 )
504 assert_mock redis
505 assert_mock notify
506 end
507 em :test_not_done_in_time
508
509 def test_ignore_frozen_ports
510 # This tests that we ignore ports in various states
511 [
512 Port.new(
513 "01",
514 "SUBMITTED",
515 nil,
516 DateTime.now - 1 * MINS,
517 "ignored",
518 "+19998887777",
519 Blather::JID.new("testroute")
520 ),
521 Port.new(
522 "01",
523 "FOC",
524 DateTime.now - 300 * MINS,
525 DateTime.now - 300 * MINS,
526 "ignored",
527 "+19998887777",
528 Blather::JID.new("testroute")
529 ),
530 Port.new(
531 "01",
532 "COMPLETED",
533 DateTime.now - 10 * MINS,
534 DateTime.now - 10 * MINS,
535 "ignored",
536 "+19998887777",
537 Blather::JID.new("testroute")
538 ),
539 Port.new(
540 "01",
541 "COMPLETED",
542 DateTime.now - 300 * MINS,
543 DateTime.now - 300 * MINS,
544 "ignored",
545 "+19998887777",
546 Blather::JID.new("testroute")
547 )
548 ].each do |port|
549 redis = Minitest::Mock.new
550 redis.expect(:exists, EMPromise.resolve(1), ["jmp_port_freeze-01"])
551
552 step = PortingStepRepo.new(redis: redis).find(port).sync
553 assert_kind_of PortingStepRepo::Frozen, step
554 end
555 end
556 em :test_ignore_frozen_ports
557
558 def test_reachability_error_during_find
559 redis = Minitest::Mock.new
560 redis.expect(:exists, EMPromise.resolve(0), ["jmp_port_freeze-01"])
561 redis.expect(:exists, "0", ["jmp_port_complete-01"])
562
563 notify = BlatherNotifyMock.new
564 notify.expect_execution(
565 "sgx", "customer info",
566 { q: "starting" }, admin_info("starting", "+19998887777").form
567 )
568
569 notify.expect_execution(
570 "sgx", "reachability",
571 { tel: "+19998887777", type: "voice" },
572 RuntimeError.new("Sender not in whitelist")
573 )
574
575 step = PortingStepRepo.new(
576 redis: redis,
577 blather_notify: notify,
578 admin_server: "sgx"
579 ).find(Port.new(
580 "01",
581 "COMPLETE",
582 DateTime.now - 25 * MINS,
583 DateTime.now - 1 * MINS,
584 "starting",
585 "+19998887777",
586 Blather::JID.new("testroute")
587 )).sync
588
589 assert_kind_of PortingStepRepo::Alert, step
590 assert_equal :reachability_failure, step.key
591 assert_nil step.real_step
592 assert_mock redis
593 assert_mock notify
594 end
595 em :test_reachability_error_during_find
596
597 def test_reachability_error_during_run_test
598 redis = Minitest::Mock.new
599 redis.expect(:exists, EMPromise.resolve(0), ["jmp_port_freeze-01"])
600 redis.expect(:exists, "0", ["jmp_port_complete-01"])
601
602 notify = BlatherNotifyMock.new
603 notify.expect_execution(
604 "sgx", "customer info",
605 { q: "starting" }, admin_info("starting", "+19998887777").form
606 )
607
608 notify.expect_execution(
609 "sgx", "reachability",
610 { tel: "+19998887777", type: "voice" },
611 FormTemplate.render("reachability_result", count: 0)
612 )
613
614 notify.expect_execution(
615 "sgx", "reachability",
616 { tel: "+19998887777", type: "voice", reachability_tel: "+15551234567" },
617 RuntimeError.new("Sender not in whitelist")
618 )
619
620 output_mock = Minitest::Mock.new
621 output_mock.expect(
622 :warn,
623 EMPromise.resolve(nil),
624 [
625 "01",
626 :reachability_failure,
627 Matching.new { |m| m =~ /Error checking.*reachability/ }
628 ]
629 )
630 output = MockOutputs.new(output_mock)
631
632 port = Port.new(
633 "01",
634 "COMPLETE",
635 DateTime.now - 25 * MINS,
636 DateTime.now - 1 * MINS,
637 "starting",
638 "+19998887777",
639 Blather::JID.new("testroute")
640 )
641
642 step = PortingStepRepo.new(
643 redis: redis,
644 blather_notify: notify,
645 admin_server: "sgx",
646 output: output,
647 testing_tel: "+15551234567"
648 ).find(port).sync
649
650 assert_kind_of(
651 PortingStepRepo::Complete::AdminCommand::GoodNumber::
652 Reachability::RunTest,
653 step
654 )
655
656 step.perform_next_step.sync
657
658 assert_mock redis
659 assert_mock notify
660 assert_mock output_mock
661 end
662 em :test_reachability_error_during_run_test
663end