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