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 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 = Conn.open(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 = Conn.open(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 = Conn.open(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 = Conn.open(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 = Conn.open(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 = Conn.open(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 = Conn.open(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 = Conn.open(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 refute Conn.open(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 = Conn.open(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 = Conn.open(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 = Conn.open(https_url, name)
385 :ok = Conn.open(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 = Conn.open(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 change owner", %{name: name} do
441 url = "https://httpbin.org"
442 :ok = Conn.open(url, name)
443 conn = Connections.checkin(url, name)
444
445 pid = Process.whereis(name)
446
447 assert :gun.info(conn).owner == pid
448 end
449
450 test "opens connection and reuse it on next request", %{name: name} do
451 url = "http://httpbin.org"
452 :ok = Conn.open(url, name)
453 Process.sleep(250)
454 conn = Connections.checkin(url, name)
455
456 assert is_pid(conn)
457 assert Process.alive?(conn)
458
459 reused_conn = Connections.checkin(url, name)
460
461 assert conn == reused_conn
462
463 %Connections{
464 conns: %{
465 "http:httpbin.org:80" => %Conn{
466 conn: ^conn,
467 gun_state: :up
468 }
469 }
470 } = Connections.get_state(name)
471 end
472
473 test "opens ssl connection and reuse it on next request", %{name: name} do
474 url = "https://httpbin.org"
475 :ok = Conn.open(url, name)
476 Process.sleep(1_000)
477 conn = Connections.checkin(url, name)
478
479 assert is_pid(conn)
480 assert Process.alive?(conn)
481
482 reused_conn = Connections.checkin(url, name)
483
484 assert conn == reused_conn
485
486 %Connections{
487 conns: %{
488 "https:httpbin.org:443" => %Conn{
489 conn: ^conn,
490 gun_state: :up
491 }
492 }
493 } = Connections.get_state(name)
494 end
495
496 test "remove frequently used and idle", %{name: name} do
497 self = self()
498 https1 = "https://www.google.com"
499 https2 = "https://httpbin.org"
500
501 :ok = Conn.open(https1, name)
502 :ok = Conn.open(https2, name)
503 Process.sleep(1_500)
504 conn = Connections.checkin(https1, name)
505
506 for _ <- 1..4 do
507 Connections.checkin(https2, name)
508 end
509
510 %Connections{
511 conns: %{
512 "https:httpbin.org:443" => %Conn{
513 conn: _,
514 gun_state: :up
515 },
516 "https:www.google.com:443" => %Conn{
517 conn: _,
518 gun_state: :up
519 }
520 }
521 } = Connections.get_state(name)
522
523 :ok = Connections.checkout(conn, self, name)
524 http = "http://httpbin.org"
525 Process.sleep(1_000)
526 :ok = Conn.open(http, name)
527 conn = Connections.checkin(http, name)
528
529 %Connections{
530 conns: %{
531 "http:httpbin.org:80" => %Conn{
532 conn: ^conn,
533 gun_state: :up
534 },
535 "https:httpbin.org:443" => %Conn{
536 conn: _,
537 gun_state: :up
538 }
539 }
540 } = Connections.get_state(name)
541 end
542
543 test "remove earlier used and idle", %{name: name} do
544 self = self()
545
546 https1 = "https://www.google.com"
547 https2 = "https://httpbin.org"
548 :ok = Conn.open(https1, name)
549 :ok = Conn.open(https2, name)
550 Process.sleep(1_500)
551
552 Connections.checkin(https1, name)
553 conn = Connections.checkin(https1, name)
554
555 Process.sleep(1_000)
556 Connections.checkin(https2, name)
557 Connections.checkin(https2, name)
558
559 %Connections{
560 conns: %{
561 "https:httpbin.org:443" => %Conn{
562 conn: _,
563 gun_state: :up
564 },
565 "https:www.google.com:443" => %Conn{
566 conn: ^conn,
567 gun_state: :up
568 }
569 }
570 } = Connections.get_state(name)
571
572 :ok = Connections.checkout(conn, self, name)
573 :ok = Connections.checkout(conn, self, name)
574
575 http = "http://httpbin.org"
576 :ok = Conn.open(http, name)
577 Process.sleep(1_000)
578
579 conn = Connections.checkin(http, name)
580
581 %Connections{
582 conns: %{
583 "http:httpbin.org:80" => %Conn{
584 conn: ^conn,
585 gun_state: :up
586 },
587 "https:httpbin.org:443" => %Conn{
588 conn: _,
589 gun_state: :up
590 }
591 }
592 } = Connections.get_state(name)
593 end
594
595 test "doesn't open new conn on pool overflow", %{name: name} do
596 self = self()
597
598 https1 = "https://www.google.com"
599 https2 = "https://httpbin.org"
600 :ok = Conn.open(https1, name)
601 :ok = Conn.open(https2, name)
602 Process.sleep(1_000)
603 Connections.checkin(https1, name)
604 conn1 = Connections.checkin(https1, name)
605 conn2 = Connections.checkin(https2, name)
606
607 %Connections{
608 conns: %{
609 "https:httpbin.org:443" => %Conn{
610 conn: ^conn2,
611 gun_state: :up,
612 conn_state: :active,
613 used_by: [{^self, _}]
614 },
615 "https:www.google.com:443" => %Conn{
616 conn: ^conn1,
617 gun_state: :up,
618 conn_state: :active,
619 used_by: [{^self, _}, {^self, _}]
620 }
621 }
622 } = Connections.get_state(name)
623
624 refute Connections.checkin("http://httpbin.org", name)
625
626 %Connections{
627 conns: %{
628 "https:httpbin.org:443" => %Conn{
629 conn: ^conn2,
630 gun_state: :up,
631 conn_state: :active,
632 used_by: [{^self, _}]
633 },
634 "https:www.google.com:443" => %Conn{
635 conn: ^conn1,
636 gun_state: :up,
637 conn_state: :active,
638 used_by: [{^self, _}, {^self, _}]
639 }
640 }
641 } = Connections.get_state(name)
642 end
643
644 test "get idle connection with the smallest crf", %{
645 name: name
646 } do
647 self = self()
648
649 https1 = "https://www.google.com"
650 https2 = "https://httpbin.org"
651
652 :ok = Conn.open(https1, name)
653 :ok = Conn.open(https2, name)
654 Process.sleep(1_500)
655 Connections.checkin(https1, name)
656 Connections.checkin(https2, name)
657 Connections.checkin(https1, name)
658 conn1 = Connections.checkin(https1, name)
659 conn2 = Connections.checkin(https2, name)
660
661 %Connections{
662 conns: %{
663 "https:httpbin.org:443" => %Conn{
664 conn: ^conn2,
665 gun_state: :up,
666 conn_state: :active,
667 used_by: [{^self, _}, {^self, _}],
668 crf: crf2
669 },
670 "https:www.google.com:443" => %Conn{
671 conn: ^conn1,
672 gun_state: :up,
673 conn_state: :active,
674 used_by: [{^self, _}, {^self, _}, {^self, _}],
675 crf: crf1
676 }
677 }
678 } = Connections.get_state(name)
679
680 assert crf1 > crf2
681
682 :ok = Connections.checkout(conn1, self, name)
683 :ok = Connections.checkout(conn1, self, name)
684 :ok = Connections.checkout(conn1, self, name)
685
686 :ok = Connections.checkout(conn2, self, name)
687 :ok = Connections.checkout(conn2, self, name)
688
689 %Connections{
690 conns: %{
691 "https:httpbin.org:443" => %Conn{
692 conn: ^conn2,
693 gun_state: :up,
694 conn_state: :idle,
695 used_by: []
696 },
697 "https:www.google.com:443" => %Conn{
698 conn: ^conn1,
699 gun_state: :up,
700 conn_state: :idle,
701 used_by: []
702 }
703 }
704 } = Connections.get_state(name)
705
706 http = "http://httpbin.org"
707 :ok = Conn.open(http, name)
708 Process.sleep(1_000)
709 conn = Connections.checkin(http, name)
710
711 %Connections{
712 conns: %{
713 "https:www.google.com:443" => %Conn{
714 conn: ^conn1,
715 gun_state: :up,
716 conn_state: :idle,
717 used_by: [],
718 crf: crf1
719 },
720 "http:httpbin.org:80" => %Conn{
721 conn: ^conn,
722 gun_state: :up,
723 conn_state: :active,
724 used_by: [{^self, _}],
725 crf: crf
726 }
727 }
728 } = Connections.get_state(name)
729
730 assert crf1 > crf
731 end
732 end
733
734 describe "with proxy" do
735 test "as ip", %{name: name} do
736 url = "http://proxy-string.com"
737 key = "http:proxy-string.com:80"
738 :ok = Conn.open(url, name, proxy: {{127, 0, 0, 1}, 8123})
739
740 conn = Connections.checkin(url, name)
741
742 %Connections{
743 conns: %{
744 ^key => %Conn{
745 conn: ^conn,
746 gun_state: :up
747 }
748 }
749 } = Connections.get_state(name)
750
751 reused_conn = Connections.checkin(url, name)
752
753 assert reused_conn == conn
754 end
755
756 test "as host", %{name: name} do
757 url = "http://proxy-tuple-atom.com"
758 :ok = Conn.open(url, name, proxy: {'localhost', 9050})
759 conn = Connections.checkin(url, name)
760
761 %Connections{
762 conns: %{
763 "http:proxy-tuple-atom.com:80" => %Conn{
764 conn: ^conn,
765 gun_state: :up
766 }
767 }
768 } = Connections.get_state(name)
769
770 reused_conn = Connections.checkin(url, name)
771
772 assert reused_conn == conn
773 end
774
775 test "as ip and ssl", %{name: name} do
776 url = "https://proxy-string.com"
777
778 :ok = Conn.open(url, name, proxy: {{127, 0, 0, 1}, 8123})
779 conn = Connections.checkin(url, name)
780
781 %Connections{
782 conns: %{
783 "https:proxy-string.com:443" => %Conn{
784 conn: ^conn,
785 gun_state: :up
786 }
787 }
788 } = Connections.get_state(name)
789
790 reused_conn = Connections.checkin(url, name)
791
792 assert reused_conn == conn
793 end
794
795 test "as host and ssl", %{name: name} do
796 url = "https://proxy-tuple-atom.com"
797 :ok = Conn.open(url, name, proxy: {'localhost', 9050})
798 conn = Connections.checkin(url, name)
799
800 %Connections{
801 conns: %{
802 "https:proxy-tuple-atom.com:443" => %Conn{
803 conn: ^conn,
804 gun_state: :up
805 }
806 }
807 } = Connections.get_state(name)
808
809 reused_conn = Connections.checkin(url, name)
810
811 assert reused_conn == conn
812 end
813
814 test "with socks type", %{name: name} do
815 url = "http://proxy-socks.com"
816
817 :ok = Conn.open(url, name, proxy: {:socks5, 'localhost', 1234})
818
819 conn = Connections.checkin(url, name)
820
821 %Connections{
822 conns: %{
823 "http:proxy-socks.com:80" => %Conn{
824 conn: ^conn,
825 gun_state: :up
826 }
827 }
828 } = Connections.get_state(name)
829
830 reused_conn = Connections.checkin(url, name)
831
832 assert reused_conn == conn
833 end
834
835 test "with socks4 type and ssl", %{name: name} do
836 url = "https://proxy-socks.com"
837
838 :ok = Conn.open(url, name, proxy: {:socks4, 'localhost', 1234})
839
840 conn = Connections.checkin(url, name)
841
842 %Connections{
843 conns: %{
844 "https:proxy-socks.com:443" => %Conn{
845 conn: ^conn,
846 gun_state: :up
847 }
848 }
849 } = Connections.get_state(name)
850
851 reused_conn = Connections.checkin(url, name)
852
853 assert reused_conn == conn
854 end
855 end
856
857 describe "crf/3" do
858 setup do
859 crf = Connections.crf(1, 10, 1)
860 {:ok, crf: crf}
861 end
862
863 test "more used will have crf higher", %{crf: crf} do
864 # used 3 times
865 crf1 = Connections.crf(1, 10, crf)
866 crf1 = Connections.crf(1, 10, crf1)
867
868 # used 2 times
869 crf2 = Connections.crf(1, 10, crf)
870
871 assert crf1 > crf2
872 end
873
874 test "recently used will have crf higher on equal references", %{crf: crf} do
875 # used 3 sec ago
876 crf1 = Connections.crf(3, 10, crf)
877
878 # used 4 sec ago
879 crf2 = Connections.crf(4, 10, crf)
880
881 assert crf1 > crf2
882 end
883
884 test "equal crf on equal reference and time", %{crf: crf} do
885 # used 2 times
886 crf1 = Connections.crf(1, 10, crf)
887
888 # used 2 times
889 crf2 = Connections.crf(1, 10, crf)
890
891 assert crf1 == crf2
892 end
893
894 test "recently used will have higher crf", %{crf: crf} do
895 crf1 = Connections.crf(2, 10, crf)
896 crf1 = Connections.crf(1, 10, crf1)
897
898 crf2 = Connections.crf(3, 10, crf)
899 crf2 = Connections.crf(4, 10, crf2)
900 assert crf1 > crf2
901 end
902 end
903
904 describe "get_unused_conns/1" do
905 test "crf is equalent, sorting by reference", %{name: name} do
906 Connections.add_conn(name, "1", %Conn{
907 conn_state: :idle,
908 last_reference: now() - 1
909 })
910
911 Connections.add_conn(name, "2", %Conn{
912 conn_state: :idle,
913 last_reference: now()
914 })
915
916 assert [{"1", _unused_conn} | _others] = Connections.get_unused_conns(name)
917 end
918
919 test "reference is equalent, sorting by crf", %{name: name} do
920 Connections.add_conn(name, "1", %Conn{
921 conn_state: :idle,
922 crf: 1.999
923 })
924
925 Connections.add_conn(name, "2", %Conn{
926 conn_state: :idle,
927 crf: 2
928 })
929
930 assert [{"1", _unused_conn} | _others] = Connections.get_unused_conns(name)
931 end
932
933 test "higher crf and lower reference", %{name: name} do
934 Connections.add_conn(name, "1", %Conn{
935 conn_state: :idle,
936 crf: 3,
937 last_reference: now() - 1
938 })
939
940 Connections.add_conn(name, "2", %Conn{
941 conn_state: :idle,
942 crf: 2,
943 last_reference: now()
944 })
945
946 assert [{"2", _unused_conn} | _others] = Connections.get_unused_conns(name)
947 end
948
949 test "lower crf and lower reference", %{name: name} do
950 Connections.add_conn(name, "1", %Conn{
951 conn_state: :idle,
952 crf: 1.99,
953 last_reference: now() - 1
954 })
955
956 Connections.add_conn(name, "2", %Conn{
957 conn_state: :idle,
958 crf: 2,
959 last_reference: now()
960 })
961
962 assert [{"1", _unused_conn} | _others] = Connections.get_unused_conns(name)
963 end
964 end
965
966 test "count/1", %{name: name} do
967 assert Connections.count(name) == 0
968 Connections.add_conn(name, "1", %Conn{conn: self()})
969 assert Connections.count(name) == 1
970 Connections.remove_conn(name, "1")
971 assert Connections.count(name) == 0
972 end
973
974 defp now do
975 :os.system_time(:second)
976 end
977 end