Merge branch 'develop' into gun
[akkoma] / test / pool / connections_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Pool.ConnectionsTest do
6 use ExUnit.Case
7 use Pleroma.Tests.Helpers
8 import ExUnit.CaptureLog
9 alias Pleroma.Gun.API
10 alias Pleroma.Gun.Conn
11 alias Pleroma.Pool.Connections
12
13 setup_all do
14 {:ok, _} = Registry.start_link(keys: :unique, name: API.Mock)
15 :ok
16 end
17
18 setup do
19 name = :test_connections
20 adapter = Application.get_env(:tesla, :adapter)
21 Application.put_env(:tesla, :adapter, Tesla.Adapter.Gun)
22 on_exit(fn -> Application.put_env(:tesla, :adapter, adapter) end)
23
24 {:ok, _pid} =
25 Connections.start_link({name, [max_connections: 2, receive_connection_timeout: 1_500]})
26
27 {:ok, name: name}
28 end
29
30 describe "alive?/2" do
31 test "is alive", %{name: name} do
32 assert Connections.alive?(name)
33 end
34
35 test "returns false if not started" do
36 refute Connections.alive?(:some_random_name)
37 end
38 end
39
40 test "opens connection and reuse it on next request", %{name: name} do
41 url = "http://some-domain.com"
42 key = "http:some-domain.com:80"
43 refute Connections.checkin(url, name)
44 :ok = Connections.open_conn(url, name)
45
46 conn = Connections.checkin(url, name)
47 assert is_pid(conn)
48 assert Process.alive?(conn)
49
50 self = self()
51
52 %Connections{
53 conns: %{
54 ^key => %Conn{
55 conn: ^conn,
56 gun_state: :up,
57 used_by: [{^self, _}],
58 conn_state: :active
59 }
60 }
61 } = Connections.get_state(name)
62
63 reused_conn = Connections.checkin(url, name)
64
65 assert conn == reused_conn
66
67 %Connections{
68 conns: %{
69 ^key => %Conn{
70 conn: ^conn,
71 gun_state: :up,
72 used_by: [{^self, _}, {^self, _}],
73 conn_state: :active
74 }
75 }
76 } = Connections.get_state(name)
77
78 :ok = Connections.checkout(conn, self, name)
79
80 %Connections{
81 conns: %{
82 ^key => %Conn{
83 conn: ^conn,
84 gun_state: :up,
85 used_by: [{^self, _}],
86 conn_state: :active
87 }
88 }
89 } = Connections.get_state(name)
90
91 :ok = Connections.checkout(conn, self, name)
92
93 %Connections{
94 conns: %{
95 ^key => %Conn{
96 conn: ^conn,
97 gun_state: :up,
98 used_by: [],
99 conn_state: :idle
100 }
101 }
102 } = Connections.get_state(name)
103 end
104
105 test "reuse connection for idna domains", %{name: name} do
106 url = "http://ですsome-domain.com"
107 refute Connections.checkin(url, name)
108
109 :ok = Connections.open_conn(url, name)
110
111 conn = Connections.checkin(url, name)
112 assert is_pid(conn)
113 assert Process.alive?(conn)
114
115 self = self()
116
117 %Connections{
118 conns: %{
119 "http:ですsome-domain.com:80" => %Conn{
120 conn: ^conn,
121 gun_state: :up,
122 used_by: [{^self, _}],
123 conn_state: :active
124 }
125 }
126 } = Connections.get_state(name)
127
128 reused_conn = Connections.checkin(url, name)
129
130 assert conn == reused_conn
131 end
132
133 test "reuse for ipv4", %{name: name} do
134 url = "http://127.0.0.1"
135
136 refute Connections.checkin(url, name)
137
138 :ok = Connections.open_conn(url, name)
139
140 conn = Connections.checkin(url, name)
141 assert is_pid(conn)
142 assert Process.alive?(conn)
143
144 self = self()
145
146 %Connections{
147 conns: %{
148 "http:127.0.0.1:80" => %Conn{
149 conn: ^conn,
150 gun_state: :up,
151 used_by: [{^self, _}],
152 conn_state: :active
153 }
154 }
155 } = Connections.get_state(name)
156
157 reused_conn = Connections.checkin(url, name)
158
159 assert conn == reused_conn
160
161 :ok = Connections.checkout(conn, self, name)
162 :ok = Connections.checkout(reused_conn, self, name)
163
164 %Connections{
165 conns: %{
166 "http:127.0.0.1:80" => %Conn{
167 conn: ^conn,
168 gun_state: :up,
169 used_by: [],
170 conn_state: :idle
171 }
172 }
173 } = Connections.get_state(name)
174 end
175
176 test "reuse for ipv6", %{name: name} do
177 url = "http://[2a03:2880:f10c:83:face:b00c:0:25de]"
178
179 refute Connections.checkin(url, name)
180
181 :ok = Connections.open_conn(url, name)
182
183 conn = Connections.checkin(url, name)
184 assert is_pid(conn)
185 assert Process.alive?(conn)
186
187 self = self()
188
189 %Connections{
190 conns: %{
191 "http:2a03:2880:f10c:83:face:b00c:0:25de:80" => %Conn{
192 conn: ^conn,
193 gun_state: :up,
194 used_by: [{^self, _}],
195 conn_state: :active
196 }
197 }
198 } = Connections.get_state(name)
199
200 reused_conn = Connections.checkin(url, name)
201
202 assert conn == reused_conn
203 end
204
205 test "up and down ipv4", %{name: name} do
206 self = self()
207 url = "http://127.0.0.1"
208 :ok = Connections.open_conn(url, name)
209 conn = Connections.checkin(url, name)
210 send(name, {:gun_down, conn, nil, nil, nil})
211 send(name, {:gun_up, conn, nil})
212
213 %Connections{
214 conns: %{
215 "http:127.0.0.1:80" => %Conn{
216 conn: ^conn,
217 gun_state: :up,
218 used_by: [{^self, _}],
219 conn_state: :active
220 }
221 }
222 } = Connections.get_state(name)
223 end
224
225 test "up and down ipv6", %{name: name} do
226 self = self()
227 url = "http://[2a03:2880:f10c:83:face:b00c:0:25de]"
228 :ok = Connections.open_conn(url, name)
229 conn = Connections.checkin(url, name)
230 send(name, {:gun_down, conn, nil, nil, nil})
231 send(name, {:gun_up, conn, nil})
232
233 %Connections{
234 conns: %{
235 "http:2a03:2880:f10c:83:face:b00c:0:25de:80" => %Conn{
236 conn: ^conn,
237 gun_state: :up,
238 used_by: [{^self, _}],
239 conn_state: :active
240 }
241 }
242 } = Connections.get_state(name)
243 end
244
245 test "reuses connection based on protocol", %{name: name} do
246 http_url = "http://some-domain.com"
247 http_key = "http:some-domain.com:80"
248 https_url = "https://some-domain.com"
249 https_key = "https:some-domain.com:443"
250
251 refute Connections.checkin(http_url, name)
252 :ok = Connections.open_conn(http_url, name)
253 conn = Connections.checkin(http_url, name)
254 assert is_pid(conn)
255 assert Process.alive?(conn)
256
257 refute Connections.checkin(https_url, name)
258 :ok = Connections.open_conn(https_url, name)
259 https_conn = Connections.checkin(https_url, name)
260
261 refute conn == https_conn
262
263 reused_https = Connections.checkin(https_url, name)
264
265 refute conn == reused_https
266
267 assert reused_https == https_conn
268
269 %Connections{
270 conns: %{
271 ^http_key => %Conn{
272 conn: ^conn,
273 gun_state: :up
274 },
275 ^https_key => %Conn{
276 conn: ^https_conn,
277 gun_state: :up
278 }
279 }
280 } = Connections.get_state(name)
281 end
282
283 test "connection can't get up", %{name: name} do
284 url = "http://gun-not-up.com"
285
286 assert capture_log(fn ->
287 :ok = Connections.open_conn(url, name)
288 refute Connections.checkin(url, name)
289 end) =~
290 "Received error on opening connection http://gun-not-up.com: {:error, :timeout}"
291 end
292
293 test "process gun_down message and then gun_up", %{name: name} do
294 self = self()
295 url = "http://gun-down-and-up.com"
296 key = "http:gun-down-and-up.com:80"
297 :ok = Connections.open_conn(url, name)
298 conn = Connections.checkin(url, name)
299
300 assert is_pid(conn)
301 assert Process.alive?(conn)
302
303 %Connections{
304 conns: %{
305 ^key => %Conn{
306 conn: ^conn,
307 gun_state: :up,
308 used_by: [{^self, _}]
309 }
310 }
311 } = Connections.get_state(name)
312
313 send(name, {:gun_down, conn, :http, nil, nil})
314
315 %Connections{
316 conns: %{
317 ^key => %Conn{
318 conn: ^conn,
319 gun_state: :down,
320 used_by: [{^self, _}]
321 }
322 }
323 } = Connections.get_state(name)
324
325 send(name, {:gun_up, conn, :http})
326
327 conn2 = Connections.checkin(url, name)
328 assert conn == conn2
329
330 assert is_pid(conn2)
331 assert Process.alive?(conn2)
332
333 %Connections{
334 conns: %{
335 ^key => %Conn{
336 conn: _,
337 gun_state: :up,
338 used_by: [{^self, _}, {^self, _}]
339 }
340 }
341 } = Connections.get_state(name)
342 end
343
344 test "async processes get same conn for same domain", %{name: name} do
345 url = "http://some-domain.com"
346 :ok = Connections.open_conn(url, name)
347
348 tasks =
349 for _ <- 1..5 do
350 Task.async(fn ->
351 Connections.checkin(url, name)
352 end)
353 end
354
355 tasks_with_results = Task.yield_many(tasks)
356
357 results =
358 Enum.map(tasks_with_results, fn {task, res} ->
359 res || Task.shutdown(task, :brutal_kill)
360 end)
361
362 conns = for {:ok, value} <- results, do: value
363
364 %Connections{
365 conns: %{
366 "http:some-domain.com:80" => %Conn{
367 conn: conn,
368 gun_state: :up
369 }
370 }
371 } = Connections.get_state(name)
372
373 assert Enum.all?(conns, fn res -> res == conn end)
374 end
375
376 test "remove frequently used and idle", %{name: name} do
377 self = self()
378 http_url = "http://some-domain.com"
379 https_url = "https://some-domain.com"
380 :ok = Connections.open_conn(https_url, name)
381 :ok = Connections.open_conn(http_url, name)
382
383 conn1 = Connections.checkin(https_url, name)
384
385 [conn2 | _conns] =
386 for _ <- 1..4 do
387 Connections.checkin(http_url, name)
388 end
389
390 http_key = "http:some-domain.com:80"
391
392 %Connections{
393 conns: %{
394 ^http_key => %Conn{
395 conn: ^conn2,
396 gun_state: :up,
397 conn_state: :active,
398 used_by: [{^self, _}, {^self, _}, {^self, _}, {^self, _}]
399 },
400 "https:some-domain.com:443" => %Conn{
401 conn: ^conn1,
402 gun_state: :up,
403 conn_state: :active,
404 used_by: [{^self, _}]
405 }
406 }
407 } = Connections.get_state(name)
408
409 :ok = Connections.checkout(conn1, self, name)
410
411 another_url = "http://another-domain.com"
412 :ok = Connections.open_conn(another_url, name)
413 conn = Connections.checkin(another_url, name)
414
415 %Connections{
416 conns: %{
417 "http:another-domain.com:80" => %Conn{
418 conn: ^conn,
419 gun_state: :up
420 },
421 ^http_key => %Conn{
422 conn: _,
423 gun_state: :up
424 }
425 }
426 } = Connections.get_state(name)
427 end
428
429 describe "integration test" do
430 @describetag :integration
431
432 clear_config([API]) do
433 Pleroma.Config.put([API], Pleroma.Gun)
434 end
435
436 test "opens connection and reuse it on next request", %{name: name} do
437 url = "http://httpbin.org"
438 :ok = Connections.open_conn(url, name)
439 Process.sleep(250)
440 conn = Connections.checkin(url, name)
441
442 assert is_pid(conn)
443 assert Process.alive?(conn)
444
445 reused_conn = Connections.checkin(url, name)
446
447 assert conn == reused_conn
448
449 %Connections{
450 conns: %{
451 "http:httpbin.org:80" => %Conn{
452 conn: ^conn,
453 gun_state: :up
454 }
455 }
456 } = Connections.get_state(name)
457 end
458
459 test "opens ssl connection and reuse it on next request", %{name: name} do
460 url = "https://httpbin.org"
461 :ok = Connections.open_conn(url, name)
462 Process.sleep(1_000)
463 conn = Connections.checkin(url, name)
464
465 assert is_pid(conn)
466 assert Process.alive?(conn)
467
468 reused_conn = Connections.checkin(url, name)
469
470 assert conn == reused_conn
471
472 %Connections{
473 conns: %{
474 "https:httpbin.org:443" => %Conn{
475 conn: ^conn,
476 gun_state: :up
477 }
478 }
479 } = Connections.get_state(name)
480 end
481
482 test "remove frequently used and idle", %{name: name} do
483 self = self()
484 https1 = "https://www.google.com"
485 https2 = "https://httpbin.org"
486
487 :ok = Connections.open_conn(https1, name)
488 :ok = Connections.open_conn(https2, name)
489 Process.sleep(1_500)
490 conn = Connections.checkin(https1, name)
491
492 for _ <- 1..4 do
493 Connections.checkin(https2, name)
494 end
495
496 %Connections{
497 conns: %{
498 "https:httpbin.org:443" => %Conn{
499 conn: _,
500 gun_state: :up
501 },
502 "https:www.google.com:443" => %Conn{
503 conn: _,
504 gun_state: :up
505 }
506 }
507 } = Connections.get_state(name)
508
509 :ok = Connections.checkout(conn, self, name)
510 http = "http://httpbin.org"
511 Process.sleep(1_000)
512 :ok = Connections.open_conn(http, name)
513 conn = Connections.checkin(http, name)
514
515 %Connections{
516 conns: %{
517 "http:httpbin.org:80" => %Conn{
518 conn: ^conn,
519 gun_state: :up
520 },
521 "https:httpbin.org:443" => %Conn{
522 conn: _,
523 gun_state: :up
524 }
525 }
526 } = Connections.get_state(name)
527 end
528
529 test "remove earlier used and idle", %{name: name} do
530 self = self()
531
532 https1 = "https://www.google.com"
533 https2 = "https://httpbin.org"
534 :ok = Connections.open_conn(https1, name)
535 :ok = Connections.open_conn(https2, name)
536 Process.sleep(1_500)
537
538 Connections.checkin(https1, name)
539 conn = Connections.checkin(https1, name)
540
541 Process.sleep(1_000)
542 Connections.checkin(https2, name)
543 Connections.checkin(https2, name)
544
545 %Connections{
546 conns: %{
547 "https:httpbin.org:443" => %Conn{
548 conn: _,
549 gun_state: :up
550 },
551 "https:www.google.com:443" => %Conn{
552 conn: ^conn,
553 gun_state: :up
554 }
555 }
556 } = Connections.get_state(name)
557
558 :ok = Connections.checkout(conn, self, name)
559 :ok = Connections.checkout(conn, self, name)
560
561 http = "http://httpbin.org"
562 :ok = Connections.open_conn(http, name)
563 Process.sleep(1_000)
564
565 conn = Connections.checkin(http, name)
566
567 %Connections{
568 conns: %{
569 "http:httpbin.org:80" => %Conn{
570 conn: ^conn,
571 gun_state: :up
572 },
573 "https:httpbin.org:443" => %Conn{
574 conn: _,
575 gun_state: :up
576 }
577 }
578 } = Connections.get_state(name)
579 end
580
581 test "doesn't open new conn on pool overflow", %{name: name} do
582 self = self()
583
584 https1 = "https://www.google.com"
585 https2 = "https://httpbin.org"
586 :ok = Connections.open_conn(https1, name)
587 :ok = Connections.open_conn(https2, name)
588 Process.sleep(1_000)
589 Connections.checkin(https1, name)
590 conn1 = Connections.checkin(https1, name)
591 conn2 = Connections.checkin(https2, name)
592
593 %Connections{
594 conns: %{
595 "https:httpbin.org:443" => %Conn{
596 conn: ^conn2,
597 gun_state: :up,
598 conn_state: :active,
599 used_by: [{^self, _}]
600 },
601 "https:www.google.com:443" => %Conn{
602 conn: ^conn1,
603 gun_state: :up,
604 conn_state: :active,
605 used_by: [{^self, _}, {^self, _}]
606 }
607 }
608 } = Connections.get_state(name)
609
610 refute Connections.checkin("http://httpbin.org", name)
611
612 %Connections{
613 conns: %{
614 "https:httpbin.org:443" => %Conn{
615 conn: ^conn2,
616 gun_state: :up,
617 conn_state: :active,
618 used_by: [{^self, _}]
619 },
620 "https:www.google.com:443" => %Conn{
621 conn: ^conn1,
622 gun_state: :up,
623 conn_state: :active,
624 used_by: [{^self, _}, {^self, _}]
625 }
626 }
627 } = Connections.get_state(name)
628 end
629
630 test "get idle connection with the smallest crf", %{
631 name: name
632 } do
633 self = self()
634
635 https1 = "https://www.google.com"
636 https2 = "https://httpbin.org"
637
638 :ok = Connections.open_conn(https1, name)
639 :ok = Connections.open_conn(https2, name)
640 Process.sleep(1_500)
641 Connections.checkin(https1, name)
642 Connections.checkin(https2, name)
643 Connections.checkin(https1, name)
644 conn1 = Connections.checkin(https1, name)
645 conn2 = Connections.checkin(https2, name)
646
647 %Connections{
648 conns: %{
649 "https:httpbin.org:443" => %Conn{
650 conn: ^conn2,
651 gun_state: :up,
652 conn_state: :active,
653 used_by: [{^self, _}, {^self, _}],
654 crf: crf2
655 },
656 "https:www.google.com:443" => %Conn{
657 conn: ^conn1,
658 gun_state: :up,
659 conn_state: :active,
660 used_by: [{^self, _}, {^self, _}, {^self, _}],
661 crf: crf1
662 }
663 }
664 } = Connections.get_state(name)
665
666 assert crf1 > crf2
667
668 :ok = Connections.checkout(conn1, self, name)
669 :ok = Connections.checkout(conn1, self, name)
670 :ok = Connections.checkout(conn1, self, name)
671
672 :ok = Connections.checkout(conn2, self, name)
673 :ok = Connections.checkout(conn2, self, name)
674
675 %Connections{
676 conns: %{
677 "https:httpbin.org:443" => %Conn{
678 conn: ^conn2,
679 gun_state: :up,
680 conn_state: :idle,
681 used_by: []
682 },
683 "https:www.google.com:443" => %Conn{
684 conn: ^conn1,
685 gun_state: :up,
686 conn_state: :idle,
687 used_by: []
688 }
689 }
690 } = Connections.get_state(name)
691
692 http = "http://httpbin.org"
693 :ok = Connections.open_conn(http, name)
694 Process.sleep(1_000)
695 conn = Connections.checkin(http, name)
696
697 %Connections{
698 conns: %{
699 "https:www.google.com:443" => %Conn{
700 conn: ^conn1,
701 gun_state: :up,
702 conn_state: :idle,
703 used_by: [],
704 crf: crf1
705 },
706 "http:httpbin.org:80" => %Conn{
707 conn: ^conn,
708 gun_state: :up,
709 conn_state: :active,
710 used_by: [{^self, _}],
711 crf: crf
712 }
713 }
714 } = Connections.get_state(name)
715
716 assert crf1 > crf
717 end
718 end
719
720 describe "with proxy" do
721 test "as ip", %{name: name} do
722 url = "http://proxy-string.com"
723 key = "http:proxy-string.com:80"
724 :ok = Connections.open_conn(url, name, proxy: {{127, 0, 0, 1}, 8123})
725
726 conn = Connections.checkin(url, name)
727
728 %Connections{
729 conns: %{
730 ^key => %Conn{
731 conn: ^conn,
732 gun_state: :up
733 }
734 }
735 } = Connections.get_state(name)
736
737 reused_conn = Connections.checkin(url, name)
738
739 assert reused_conn == conn
740 end
741
742 test "as host", %{name: name} do
743 url = "http://proxy-tuple-atom.com"
744 :ok = Connections.open_conn(url, name, proxy: {'localhost', 9050})
745 conn = Connections.checkin(url, name)
746
747 %Connections{
748 conns: %{
749 "http:proxy-tuple-atom.com:80" => %Conn{
750 conn: ^conn,
751 gun_state: :up
752 }
753 }
754 } = Connections.get_state(name)
755
756 reused_conn = Connections.checkin(url, name)
757
758 assert reused_conn == conn
759 end
760
761 test "as ip and ssl", %{name: name} do
762 url = "https://proxy-string.com"
763
764 :ok = Connections.open_conn(url, name, proxy: {{127, 0, 0, 1}, 8123})
765 conn = Connections.checkin(url, name)
766
767 %Connections{
768 conns: %{
769 "https:proxy-string.com:443" => %Conn{
770 conn: ^conn,
771 gun_state: :up
772 }
773 }
774 } = Connections.get_state(name)
775
776 reused_conn = Connections.checkin(url, name)
777
778 assert reused_conn == conn
779 end
780
781 test "as host and ssl", %{name: name} do
782 url = "https://proxy-tuple-atom.com"
783 :ok = Connections.open_conn(url, name, proxy: {'localhost', 9050})
784 conn = Connections.checkin(url, name)
785
786 %Connections{
787 conns: %{
788 "https:proxy-tuple-atom.com:443" => %Conn{
789 conn: ^conn,
790 gun_state: :up
791 }
792 }
793 } = Connections.get_state(name)
794
795 reused_conn = Connections.checkin(url, name)
796
797 assert reused_conn == conn
798 end
799
800 test "with socks type", %{name: name} do
801 url = "http://proxy-socks.com"
802
803 :ok = Connections.open_conn(url, name, proxy: {:socks5, 'localhost', 1234})
804
805 conn = Connections.checkin(url, name)
806
807 %Connections{
808 conns: %{
809 "http:proxy-socks.com:80" => %Conn{
810 conn: ^conn,
811 gun_state: :up
812 }
813 }
814 } = Connections.get_state(name)
815
816 reused_conn = Connections.checkin(url, name)
817
818 assert reused_conn == conn
819 end
820
821 test "with socks4 type and ssl", %{name: name} do
822 url = "https://proxy-socks.com"
823
824 :ok = Connections.open_conn(url, name, proxy: {:socks4, 'localhost', 1234})
825
826 conn = Connections.checkin(url, name)
827
828 %Connections{
829 conns: %{
830 "https:proxy-socks.com:443" => %Conn{
831 conn: ^conn,
832 gun_state: :up
833 }
834 }
835 } = Connections.get_state(name)
836
837 reused_conn = Connections.checkin(url, name)
838
839 assert reused_conn == conn
840 end
841 end
842
843 describe "crf/3" do
844 setup do
845 crf = Connections.crf(1, 10, 1)
846 {:ok, crf: crf}
847 end
848
849 test "more used will have crf higher", %{crf: crf} do
850 # used 3 times
851 crf1 = Connections.crf(1, 10, crf)
852 crf1 = Connections.crf(1, 10, crf1)
853
854 # used 2 times
855 crf2 = Connections.crf(1, 10, crf)
856
857 assert crf1 > crf2
858 end
859
860 test "recently used will have crf higher on equal references", %{crf: crf} do
861 # used 3 sec ago
862 crf1 = Connections.crf(3, 10, crf)
863
864 # used 4 sec ago
865 crf2 = Connections.crf(4, 10, crf)
866
867 assert crf1 > crf2
868 end
869
870 test "equal crf on equal reference and time", %{crf: crf} do
871 # used 2 times
872 crf1 = Connections.crf(1, 10, crf)
873
874 # used 2 times
875 crf2 = Connections.crf(1, 10, crf)
876
877 assert crf1 == crf2
878 end
879
880 test "recently used will have higher crf", %{crf: crf} do
881 crf1 = Connections.crf(2, 10, crf)
882 crf1 = Connections.crf(1, 10, crf1)
883
884 crf2 = Connections.crf(3, 10, crf)
885 crf2 = Connections.crf(4, 10, crf2)
886 assert crf1 > crf2
887 end
888 end
889
890 describe "get_unused_conns/1" do
891 test "crf is equalent, sorting by reference" do
892 conns = %{
893 "1" => %Conn{
894 conn_state: :idle,
895 last_reference: now() - 1
896 },
897 "2" => %Conn{
898 conn_state: :idle,
899 last_reference: now()
900 }
901 }
902
903 assert [{"1", _unused_conn} | _others] = Connections.get_unused_conns(conns)
904 end
905
906 test "reference is equalent, sorting by crf" do
907 conns = %{
908 "1" => %Conn{
909 conn_state: :idle,
910 crf: 1.999
911 },
912 "2" => %Conn{
913 conn_state: :idle,
914 crf: 2
915 }
916 }
917
918 assert [{"1", _unused_conn} | _others] = Connections.get_unused_conns(conns)
919 end
920
921 test "higher crf and lower reference" do
922 conns = %{
923 "1" => %Conn{
924 conn_state: :idle,
925 crf: 3,
926 last_reference: now() - 1
927 },
928 "2" => %Conn{
929 conn_state: :idle,
930 crf: 2,
931 last_reference: now()
932 }
933 }
934
935 assert [{"2", _unused_conn} | _others] = Connections.get_unused_conns(conns)
936 end
937
938 test "lower crf and lower reference" do
939 conns = %{
940 "1" => %Conn{
941 conn_state: :idle,
942 crf: 1.99,
943 last_reference: now() - 1
944 },
945 "2" => %Conn{
946 conn_state: :idle,
947 crf: 2,
948 last_reference: now()
949 }
950 }
951
952 assert [{"1", _unused_conn} | _others] = Connections.get_unused_conns(conns)
953 end
954 end
955
956 defp now do
957 :os.system_time(:second)
958 end
959 end