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_number_does_not_invoke_reachability
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", "+19998881111").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 before backend 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 "9998887777",
247 Blather::JID.new("testroute")
248 )).sync
249
250 assert_kind_of(
251 PortingStepRepo::Complete::AdminCommand::WrongNumber, step
252 )
253 end
254 em :test_change_number_does_not_invoke_reachability
255
256 def test_unknown_customer
257 redis = Minitest::Mock.new
258 redis.expect(:exists, EMPromise.resolve(0), ["jmp_port_freeze-01"])
259 redis.expect(:exists, "0", ["jmp_port_complete-01"])
260
261 notify = BlatherNotifyMock.new
262 notify.expect_execution(
263 "sgx", "customer info",
264 { q: "unknown_customer" }, admin_info("unknown_customer", nil).form
265 )
266
267 step = PortingStepRepo.new(
268 redis: redis,
269 blather_notify: notify,
270 admin_server: "sgx"
271 ).find(Port.new(
272 "01",
273 "COMPLETE",
274 DateTime.now - 25 * MINS,
275 DateTime.now - 1 * MINS,
276 "unknown_customer",
277 "9998887777",
278 Blather::JID.new("testroute")
279 )).sync
280
281 assert_kind_of PortingStepRepo::Alert, step
282 assert_equal :port_for_unknown_customer, step.key
283 assert_kind_of PortingStepRepo::Complete::NoCustomer, step.real_step
284 assert_mock redis
285 assert_mock notify
286 end
287 em :test_unknown_customer
288
289 def test_first_reachability
290 redis = Minitest::Mock.new
291 redis.expect(:exists, EMPromise.resolve(0), ["jmp_port_freeze-01"])
292 redis.expect(:exists, "0", ["jmp_port_complete-01"])
293
294 notify = BlatherNotifyMock.new
295 notify.expect_execution(
296 "sgx", "customer info",
297 { q: "starting" }, admin_info("starting", "+19998887777").form
298 )
299
300 notify.expect_execution(
301 "sgx", "reachability",
302 { tel: "+19998887777", type: "voice" },
303 FormTemplate.render("reachability_result", count: 0)
304 )
305
306 step = PortingStepRepo.new(
307 redis: redis,
308 blather_notify: notify,
309 admin_server: "sgx"
310 ).find(Port.new(
311 "01",
312 "COMPLETE",
313 DateTime.now - 25 * MINS,
314 DateTime.now - 1 * MINS,
315 "starting",
316 "+19998887777",
317 Blather::JID.new("testroute")
318 )).sync
319
320 assert_kind_of(
321 PortingStepRepo::Complete::AdminCommand::GoodNumber::
322 Reachability::RunTest,
323 step
324 )
325 assert_equal "voice", step.type
326 assert_mock redis
327 assert_mock notify
328 end
329 em :test_first_reachability
330
331 def test_reach_sms_reachability
332 redis = Minitest::Mock.new
333 redis.expect(:exists, EMPromise.resolve(0), ["jmp_port_freeze-01"])
334 redis.expect(:exists, "0", ["jmp_port_complete-01"])
335
336 notify = BlatherNotifyMock.new
337 notify.expect_execution(
338 "sgx", "customer info",
339 { q: "starting" }, admin_info("starting", "+19998887777").form
340 )
341
342 notify.expect_execution(
343 "sgx", "reachability",
344 { tel: "+19998887777", type: "voice" },
345 FormTemplate.render("reachability_result", count: 1)
346 )
347
348 notify.expect_execution(
349 "sgx", "reachability",
350 { tel: "+19998887777", type: "sms" },
351 FormTemplate.render("reachability_result", count: 0)
352 )
353
354 step = PortingStepRepo.new(
355 redis: redis,
356 blather_notify: notify,
357 admin_server: "sgx"
358 ).find(Port.new(
359 "01",
360 "COMPLETE",
361 DateTime.now - 25 * MINS,
362 DateTime.now - 1 * MINS,
363 "starting",
364 "+19998887777",
365 Blather::JID.new("testroute")
366 )).sync
367
368 assert_kind_of(
369 PortingStepRepo::Complete::AdminCommand::GoodNumber::
370 Reachability::RunTest,
371 step
372 )
373 assert_equal "sms", step.type
374 assert_mock redis
375 assert_mock notify
376 end
377 em :test_reach_sms_reachability
378
379 def test_all_reachable
380 redis = Minitest::Mock.new
381 redis.expect(:exists, EMPromise.resolve(0), ["jmp_port_freeze-01"])
382 redis.expect(:exists, "0", ["jmp_port_complete-01"])
383
384 notify = BlatherNotifyMock.new
385 notify.expect_execution(
386 "sgx", "customer info",
387 { q: "starting" }, admin_info("starting", "+19998887777").form
388 )
389
390 notify.expect_execution(
391 "sgx", "reachability",
392 { tel: "+19998887777", type: "voice" },
393 FormTemplate.render("reachability_result", count: 1)
394 )
395
396 notify.expect_execution(
397 "sgx", "reachability",
398 { tel: "+19998887777", type: "sms" },
399 FormTemplate.render("reachability_result", count: 1)
400 )
401
402 step = PortingStepRepo.new(
403 redis: redis,
404 blather_notify: notify,
405 admin_server: "sgx"
406 ).find(Port.new(
407 "01",
408 "COMPLETE",
409 DateTime.now - 25 * MINS,
410 DateTime.now - 1 * MINS,
411 "starting",
412 "+19998887777",
413 Blather::JID.new("testroute")
414 )).sync
415
416 assert_kind_of(
417 PortingStepRepo::Complete::AdminCommand::GoodNumber::FinishUp,
418 step
419 )
420 assert_mock redis
421 assert_mock notify
422 end
423 em :test_all_reachable
424
425 def test_not_done_in_time
426 redis = Minitest::Mock.new
427 redis.expect(:exists, EMPromise.resolve(0), ["jmp_port_freeze-01"])
428 redis.expect(:exists, "0", ["jmp_port_complete-01"])
429
430 notify = BlatherNotifyMock.new
431 notify.expect_execution(
432 "sgx", "customer info",
433 { q: "starting" }, admin_info("starting", "+19998887777").form
434 )
435
436 notify.expect_execution(
437 "sgx", "reachability",
438 { tel: "+19998887777", type: "voice" },
439 FormTemplate.render("reachability_result", count: 0)
440 )
441
442 step = PortingStepRepo.new(
443 redis: redis,
444 blather_notify: notify,
445 admin_server: "sgx"
446 ).find(Port.new(
447 "01",
448 "COMPLETE",
449 DateTime.now - 55 * MINS,
450 DateTime.now - 50 * MINS,
451 "starting",
452 "+19998887777",
453 Blather::JID.new("testroute")
454 )).sync
455
456 assert_kind_of PortingStepRepo::Alert, step
457 assert_equal :late_finish, step.key
458 assert_kind_of(
459 PortingStepRepo::Complete::AdminCommand::GoodNumber::
460 Reachability::RunTest,
461 step.real_step
462 )
463 assert_mock redis
464 assert_mock notify
465 end
466 em :test_not_done_in_time
467
468 def test_ignore_frozen_ports
469 # This tests that we ignore ports in various states
470 [
471 Port.new(
472 "01",
473 "SUBMITTED",
474 nil,
475 DateTime.now - 1 * MINS,
476 "ignored",
477 "+19998887777",
478 Blather::JID.new("testroute")
479 ),
480 Port.new(
481 "01",
482 "FOC",
483 DateTime.now - 300 * MINS,
484 DateTime.now - 300 * MINS,
485 "ignored",
486 "+19998887777",
487 Blather::JID.new("testroute")
488 ),
489 Port.new(
490 "01",
491 "COMPLETED",
492 DateTime.now - 10 * MINS,
493 DateTime.now - 10 * MINS,
494 "ignored",
495 "+19998887777",
496 Blather::JID.new("testroute")
497 ),
498 Port.new(
499 "01",
500 "COMPLETED",
501 DateTime.now - 300 * MINS,
502 DateTime.now - 300 * MINS,
503 "ignored",
504 "+19998887777",
505 Blather::JID.new("testroute")
506 )
507 ].each do |port|
508 redis = Minitest::Mock.new
509 redis.expect(:exists, EMPromise.resolve(1), ["jmp_port_freeze-01"])
510
511 step = PortingStepRepo.new(redis: redis).find(port).sync
512 assert_kind_of PortingStepRepo::Frozen, step
513 end
514 end
515 em :test_ignore_frozen_ports
516
517 def test_reachability_error_during_find
518 redis = Minitest::Mock.new
519 redis.expect(:exists, EMPromise.resolve(0), ["jmp_port_freeze-01"])
520 redis.expect(:exists, "0", ["jmp_port_complete-01"])
521
522 notify = BlatherNotifyMock.new
523 notify.expect_execution(
524 "sgx", "customer info",
525 { q: "starting" }, admin_info("starting", "+19998887777").form
526 )
527
528 notify.expect_execution(
529 "sgx", "reachability",
530 { tel: "+19998887777", type: "voice" },
531 RuntimeError.new("Sender not in whitelist")
532 )
533
534 step = PortingStepRepo.new(
535 redis: redis,
536 blather_notify: notify,
537 admin_server: "sgx"
538 ).find(Port.new(
539 "01",
540 "COMPLETE",
541 DateTime.now - 25 * MINS,
542 DateTime.now - 1 * MINS,
543 "starting",
544 "+19998887777",
545 Blather::JID.new("testroute")
546 )).sync
547
548 assert_kind_of PortingStepRepo::Alert, step
549 assert_equal :reachability_failure, step.key
550 assert_nil step.real_step
551 assert_mock redis
552 assert_mock notify
553 end
554 em :test_reachability_error_during_find
555
556 def test_reachability_error_during_run_test
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 FormTemplate.render("reachability_result", count: 0)
571 )
572
573 notify.expect_execution(
574 "sgx", "reachability",
575 { tel: "+19998887777", type: "voice", reachability_tel: "+15551234567" },
576 RuntimeError.new("Sender not in whitelist")
577 )
578
579 output_mock = Minitest::Mock.new
580 output_mock.expect(
581 :warn,
582 EMPromise.resolve(nil),
583 [
584 "01",
585 :reachability_failure,
586 Matching.new { |m| m =~ /Error checking.*reachability/ }
587 ]
588 )
589 output = MockOutputs.new(output_mock)
590
591 port = Port.new(
592 "01",
593 "COMPLETE",
594 DateTime.now - 25 * MINS,
595 DateTime.now - 1 * MINS,
596 "starting",
597 "+19998887777",
598 Blather::JID.new("testroute")
599 )
600
601 step = PortingStepRepo.new(
602 redis: redis,
603 blather_notify: notify,
604 admin_server: "sgx",
605 output: output,
606 testing_tel: "+15551234567"
607 ).find(port).sync
608
609 assert_kind_of(
610 PortingStepRepo::Complete::AdminCommand::GoodNumber::
611 Reachability::RunTest,
612 step
613 )
614
615 step.perform_next_step.sync
616
617 assert_mock redis
618 assert_mock notify
619 assert_mock output_mock
620 end
621 em :test_reachability_error_during_run_test
622end