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