Merge branch 'develop' into issue/1276
[akkoma] / test / pool / connections_test.exs
index 6f0e041ae7a7640b8675c55720c758cb58a388d5..aeda54875851f171f06505596a7586ce6b02db61 100644 (file)
@@ -1,32 +1,85 @@
 # Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Pool.ConnectionsTest do
-  use ExUnit.Case
+  use ExUnit.Case, async: true
   use Pleroma.Tests.Helpers
+
   import ExUnit.CaptureLog
-  alias Pleroma.Gun.API
+  import Mox
+
   alias Pleroma.Gun.Conn
+  alias Pleroma.GunMock
   alias Pleroma.Pool.Connections
 
-  setup_all do
-    {:ok, _} = Registry.start_link(keys: :unique, name: API.Mock)
-    :ok
-  end
+  setup :verify_on_exit!
 
-  setup do
+  setup_all do
     name = :test_connections
-    adapter = Application.get_env(:tesla, :adapter)
-    Application.put_env(:tesla, :adapter, Tesla.Adapter.Gun)
-    on_exit(fn -> Application.put_env(:tesla, :adapter, adapter) end)
+    {:ok, pid} = Connections.start_link({name, [checkin_timeout: 150]})
+    {:ok, _} = Registry.start_link(keys: :unique, name: Pleroma.GunMock)
 
-    {:ok, _pid} =
-      Connections.start_link({name, [max_connections: 2, receive_connection_timeout: 1_500]})
+    on_exit(fn ->
+      if Process.alive?(pid), do: GenServer.stop(name)
+    end)
 
     {:ok, name: name}
   end
 
+  defp open_mock(num \\ 1) do
+    GunMock
+    |> expect(:open, num, &start_and_register(&1, &2, &3))
+    |> expect(:await_up, num, fn _, _ -> {:ok, :http} end)
+    |> expect(:set_owner, num, fn _, _ -> :ok end)
+  end
+
+  defp connect_mock(mock) do
+    mock
+    |> expect(:connect, &connect(&1, &2))
+    |> expect(:await, &await(&1, &2))
+  end
+
+  defp info_mock(mock), do: expect(mock, :info, &info(&1))
+
+  defp start_and_register('gun-not-up.com', _, _), do: {:error, :timeout}
+
+  defp start_and_register(host, port, _) do
+    {:ok, pid} = Task.start_link(fn -> Process.sleep(1000) end)
+
+    scheme =
+      case port do
+        443 -> "https"
+        _ -> "http"
+      end
+
+    Registry.register(GunMock, pid, %{
+      origin_scheme: scheme,
+      origin_host: host,
+      origin_port: port
+    })
+
+    {:ok, pid}
+  end
+
+  defp info(pid) do
+    [{_, info}] = Registry.lookup(GunMock, pid)
+    info
+  end
+
+  defp connect(pid, _) do
+    ref = make_ref()
+    Registry.register(GunMock, ref, pid)
+    ref
+  end
+
+  defp await(pid, ref) do
+    [{_, ^pid}] = Registry.lookup(GunMock, ref)
+    {:response, :fin, 200, []}
+  end
+
+  defp now, do: :os.system_time(:second)
+
   describe "alive?/2" do
     test "is alive", %{name: name} do
       assert Connections.alive?(name)
@@ -38,10 +91,11 @@ defmodule Pleroma.Pool.ConnectionsTest do
   end
 
   test "opens connection and reuse it on next request", %{name: name} do
+    open_mock()
     url = "http://some-domain.com"
     key = "http:some-domain.com:80"
     refute Connections.checkin(url, name)
-    :ok = Connections.open_conn(url, name)
+    :ok = Conn.open(url, name)
 
     conn = Connections.checkin(url, name)
     assert is_pid(conn)
@@ -103,10 +157,11 @@ defmodule Pleroma.Pool.ConnectionsTest do
   end
 
   test "reuse connection for idna domains", %{name: name} do
+    open_mock()
     url = "http://ですsome-domain.com"
     refute Connections.checkin(url, name)
 
-    :ok = Connections.open_conn(url, name)
+    :ok = Conn.open(url, name)
 
     conn = Connections.checkin(url, name)
     assert is_pid(conn)
@@ -131,11 +186,12 @@ defmodule Pleroma.Pool.ConnectionsTest do
   end
 
   test "reuse for ipv4", %{name: name} do
+    open_mock()
     url = "http://127.0.0.1"
 
     refute Connections.checkin(url, name)
 
-    :ok = Connections.open_conn(url, name)
+    :ok = Conn.open(url, name)
 
     conn = Connections.checkin(url, name)
     assert is_pid(conn)
@@ -174,11 +230,12 @@ defmodule Pleroma.Pool.ConnectionsTest do
   end
 
   test "reuse for ipv6", %{name: name} do
+    open_mock()
     url = "http://[2a03:2880:f10c:83:face:b00c:0:25de]"
 
     refute Connections.checkin(url, name)
 
-    :ok = Connections.open_conn(url, name)
+    :ok = Conn.open(url, name)
 
     conn = Connections.checkin(url, name)
     assert is_pid(conn)
@@ -203,9 +260,13 @@ defmodule Pleroma.Pool.ConnectionsTest do
   end
 
   test "up and down ipv4", %{name: name} do
+    open_mock()
+    |> info_mock()
+    |> allow(self(), name)
+
     self = self()
     url = "http://127.0.0.1"
-    :ok = Connections.open_conn(url, name)
+    :ok = Conn.open(url, name)
     conn = Connections.checkin(url, name)
     send(name, {:gun_down, conn, nil, nil, nil})
     send(name, {:gun_up, conn, nil})
@@ -224,8 +285,13 @@ defmodule Pleroma.Pool.ConnectionsTest do
 
   test "up and down ipv6", %{name: name} do
     self = self()
+
+    open_mock()
+    |> info_mock()
+    |> allow(self, name)
+
     url = "http://[2a03:2880:f10c:83:face:b00c:0:25de]"
-    :ok = Connections.open_conn(url, name)
+    :ok = Conn.open(url, name)
     conn = Connections.checkin(url, name)
     send(name, {:gun_down, conn, nil, nil, nil})
     send(name, {:gun_up, conn, nil})
@@ -243,19 +309,20 @@ defmodule Pleroma.Pool.ConnectionsTest do
   end
 
   test "reuses connection based on protocol", %{name: name} do
+    open_mock(2)
     http_url = "http://some-domain.com"
     http_key = "http:some-domain.com:80"
     https_url = "https://some-domain.com"
     https_key = "https:some-domain.com:443"
 
     refute Connections.checkin(http_url, name)
-    :ok = Connections.open_conn(http_url, name)
+    :ok = Conn.open(http_url, name)
     conn = Connections.checkin(http_url, name)
     assert is_pid(conn)
     assert Process.alive?(conn)
 
     refute Connections.checkin(https_url, name)
-    :ok = Connections.open_conn(https_url, name)
+    :ok = Conn.open(https_url, name)
     https_conn = Connections.checkin(https_url, name)
 
     refute conn == https_conn
@@ -281,20 +348,26 @@ defmodule Pleroma.Pool.ConnectionsTest do
   end
 
   test "connection can't get up", %{name: name} do
+    expect(GunMock, :open, &start_and_register(&1, &2, &3))
     url = "http://gun-not-up.com"
 
     assert capture_log(fn ->
-             :ok = Connections.open_conn(url, name)
+             refute Conn.open(url, name)
              refute Connections.checkin(url, name)
            end) =~
-             "Received error on opening connection http://gun-not-up.com: {:error, :timeout}"
+             "Opening connection to http://gun-not-up.com failed with error {:error, :timeout}"
   end
 
   test "process gun_down message and then gun_up", %{name: name} do
     self = self()
+
+    open_mock()
+    |> info_mock()
+    |> allow(self, name)
+
     url = "http://gun-down-and-up.com"
     key = "http:gun-down-and-up.com:80"
-    :ok = Connections.open_conn(url, name)
+    :ok = Conn.open(url, name)
     conn = Connections.checkin(url, name)
 
     assert is_pid(conn)
@@ -342,8 +415,9 @@ defmodule Pleroma.Pool.ConnectionsTest do
   end
 
   test "async processes get same conn for same domain", %{name: name} do
+    open_mock()
     url = "http://some-domain.com"
-    :ok = Connections.open_conn(url, name)
+    :ok = Conn.open(url, name)
 
     tasks =
       for _ <- 1..5 do
@@ -374,11 +448,12 @@ defmodule Pleroma.Pool.ConnectionsTest do
   end
 
   test "remove frequently used and idle", %{name: name} do
+    open_mock(3)
     self = self()
     http_url = "http://some-domain.com"
     https_url = "https://some-domain.com"
-    :ok = Connections.open_conn(https_url, name)
-    :ok = Connections.open_conn(http_url, name)
+    :ok = Conn.open(https_url, name)
+    :ok = Conn.open(http_url, name)
 
     conn1 = Connections.checkin(https_url, name)
 
@@ -409,7 +484,7 @@ defmodule Pleroma.Pool.ConnectionsTest do
     :ok = Connections.checkout(conn1, self, name)
 
     another_url = "http://another-domain.com"
-    :ok = Connections.open_conn(another_url, name)
+    :ok = Conn.open(another_url, name)
     conn = Connections.checkin(another_url, name)
 
     %Connections{
@@ -426,302 +501,14 @@ defmodule Pleroma.Pool.ConnectionsTest do
     } = Connections.get_state(name)
   end
 
-  describe "integration test" do
-    @describetag :integration
-
-    clear_config([API]) do
-      Pleroma.Config.put([API], Pleroma.Gun)
-    end
-
-    test "opens connection and reuse it on next request", %{name: name} do
-      url = "http://httpbin.org"
-      :ok = Connections.open_conn(url, name)
-      Process.sleep(250)
-      conn = Connections.checkin(url, name)
-
-      assert is_pid(conn)
-      assert Process.alive?(conn)
-
-      reused_conn = Connections.checkin(url, name)
-
-      assert conn == reused_conn
-
-      %Connections{
-        conns: %{
-          "http:httpbin.org:80" => %Conn{
-            conn: ^conn,
-            gun_state: :up
-          }
-        }
-      } = Connections.get_state(name)
-    end
-
-    test "opens ssl connection and reuse it on next request", %{name: name} do
-      url = "https://httpbin.org"
-      :ok = Connections.open_conn(url, name)
-      Process.sleep(1_000)
-      conn = Connections.checkin(url, name)
-
-      assert is_pid(conn)
-      assert Process.alive?(conn)
-
-      reused_conn = Connections.checkin(url, name)
-
-      assert conn == reused_conn
-
-      %Connections{
-        conns: %{
-          "https:httpbin.org:443" => %Conn{
-            conn: ^conn,
-            gun_state: :up
-          }
-        }
-      } = Connections.get_state(name)
-    end
-
-    test "remove frequently used and idle", %{name: name} do
-      self = self()
-      https1 = "https://www.google.com"
-      https2 = "https://httpbin.org"
-
-      :ok = Connections.open_conn(https1, name)
-      :ok = Connections.open_conn(https2, name)
-      Process.sleep(1_500)
-      conn = Connections.checkin(https1, name)
-
-      for _ <- 1..4 do
-        Connections.checkin(https2, name)
-      end
-
-      %Connections{
-        conns: %{
-          "https:httpbin.org:443" => %Conn{
-            conn: _,
-            gun_state: :up
-          },
-          "https:www.google.com:443" => %Conn{
-            conn: _,
-            gun_state: :up
-          }
-        }
-      } = Connections.get_state(name)
-
-      :ok = Connections.checkout(conn, self, name)
-      http = "http://httpbin.org"
-      Process.sleep(1_000)
-      :ok = Connections.open_conn(http, name)
-      conn = Connections.checkin(http, name)
-
-      %Connections{
-        conns: %{
-          "http:httpbin.org:80" => %Conn{
-            conn: ^conn,
-            gun_state: :up
-          },
-          "https:httpbin.org:443" => %Conn{
-            conn: _,
-            gun_state: :up
-          }
-        }
-      } = Connections.get_state(name)
-    end
-
-    test "remove earlier used and idle", %{name: name} do
-      self = self()
-
-      https1 = "https://www.google.com"
-      https2 = "https://httpbin.org"
-      :ok = Connections.open_conn(https1, name)
-      :ok = Connections.open_conn(https2, name)
-      Process.sleep(1_500)
-
-      Connections.checkin(https1, name)
-      conn = Connections.checkin(https1, name)
-
-      Process.sleep(1_000)
-      Connections.checkin(https2, name)
-      Connections.checkin(https2, name)
-
-      %Connections{
-        conns: %{
-          "https:httpbin.org:443" => %Conn{
-            conn: _,
-            gun_state: :up
-          },
-          "https:www.google.com:443" => %Conn{
-            conn: ^conn,
-            gun_state: :up
-          }
-        }
-      } = Connections.get_state(name)
-
-      :ok = Connections.checkout(conn, self, name)
-      :ok = Connections.checkout(conn, self, name)
-
-      http = "http://httpbin.org"
-      :ok = Connections.open_conn(http, name)
-      Process.sleep(1_000)
-
-      conn = Connections.checkin(http, name)
-
-      %Connections{
-        conns: %{
-          "http:httpbin.org:80" => %Conn{
-            conn: ^conn,
-            gun_state: :up
-          },
-          "https:httpbin.org:443" => %Conn{
-            conn: _,
-            gun_state: :up
-          }
-        }
-      } = Connections.get_state(name)
-    end
-
-    test "doesn't open new conn on pool overflow", %{name: name} do
-      self = self()
-
-      https1 = "https://www.google.com"
-      https2 = "https://httpbin.org"
-      :ok = Connections.open_conn(https1, name)
-      :ok = Connections.open_conn(https2, name)
-      Process.sleep(1_000)
-      Connections.checkin(https1, name)
-      conn1 = Connections.checkin(https1, name)
-      conn2 = Connections.checkin(https2, name)
-
-      %Connections{
-        conns: %{
-          "https:httpbin.org:443" => %Conn{
-            conn: ^conn2,
-            gun_state: :up,
-            conn_state: :active,
-            used_by: [{^self, _}]
-          },
-          "https:www.google.com:443" => %Conn{
-            conn: ^conn1,
-            gun_state: :up,
-            conn_state: :active,
-            used_by: [{^self, _}, {^self, _}]
-          }
-        }
-      } = Connections.get_state(name)
-
-      refute Connections.checkin("http://httpbin.org", name)
-
-      %Connections{
-        conns: %{
-          "https:httpbin.org:443" => %Conn{
-            conn: ^conn2,
-            gun_state: :up,
-            conn_state: :active,
-            used_by: [{^self, _}]
-          },
-          "https:www.google.com:443" => %Conn{
-            conn: ^conn1,
-            gun_state: :up,
-            conn_state: :active,
-            used_by: [{^self, _}, {^self, _}]
-          }
-        }
-      } = Connections.get_state(name)
-    end
-
-    test "get idle connection with the smallest crf", %{
-      name: name
-    } do
-      self = self()
-
-      https1 = "https://www.google.com"
-      https2 = "https://httpbin.org"
-
-      :ok = Connections.open_conn(https1, name)
-      :ok = Connections.open_conn(https2, name)
-      Process.sleep(1_500)
-      Connections.checkin(https1, name)
-      Connections.checkin(https2, name)
-      Connections.checkin(https1, name)
-      conn1 = Connections.checkin(https1, name)
-      conn2 = Connections.checkin(https2, name)
-
-      %Connections{
-        conns: %{
-          "https:httpbin.org:443" => %Conn{
-            conn: ^conn2,
-            gun_state: :up,
-            conn_state: :active,
-            used_by: [{^self, _}, {^self, _}],
-            crf: crf2
-          },
-          "https:www.google.com:443" => %Conn{
-            conn: ^conn1,
-            gun_state: :up,
-            conn_state: :active,
-            used_by: [{^self, _}, {^self, _}, {^self, _}],
-            crf: crf1
-          }
-        }
-      } = Connections.get_state(name)
-
-      assert crf1 > crf2
-
-      :ok = Connections.checkout(conn1, self, name)
-      :ok = Connections.checkout(conn1, self, name)
-      :ok = Connections.checkout(conn1, self, name)
-
-      :ok = Connections.checkout(conn2, self, name)
-      :ok = Connections.checkout(conn2, self, name)
-
-      %Connections{
-        conns: %{
-          "https:httpbin.org:443" => %Conn{
-            conn: ^conn2,
-            gun_state: :up,
-            conn_state: :idle,
-            used_by: []
-          },
-          "https:www.google.com:443" => %Conn{
-            conn: ^conn1,
-            gun_state: :up,
-            conn_state: :idle,
-            used_by: []
-          }
-        }
-      } = Connections.get_state(name)
-
-      http = "http://httpbin.org"
-      :ok = Connections.open_conn(http, name)
-      Process.sleep(1_000)
-      conn = Connections.checkin(http, name)
-
-      %Connections{
-        conns: %{
-          "https:www.google.com:443" => %Conn{
-            conn: ^conn1,
-            gun_state: :up,
-            conn_state: :idle,
-            used_by: [],
-            crf: crf1
-          },
-          "http:httpbin.org:80" => %Conn{
-            conn: ^conn,
-            gun_state: :up,
-            conn_state: :active,
-            used_by: [{^self, _}],
-            crf: crf
-          }
-        }
-      } = Connections.get_state(name)
-
-      assert crf1 > crf
-    end
-  end
-
   describe "with proxy" do
     test "as ip", %{name: name} do
+      open_mock()
+      |> connect_mock()
+
       url = "http://proxy-string.com"
       key = "http:proxy-string.com:80"
-      :ok = Connections.open_conn(url, name, proxy: {{127, 0, 0, 1}, 8123})
+      :ok = Conn.open(url, name, proxy: {{127, 0, 0, 1}, 8123})
 
       conn = Connections.checkin(url, name)
 
@@ -740,8 +527,11 @@ defmodule Pleroma.Pool.ConnectionsTest do
     end
 
     test "as host", %{name: name} do
+      open_mock()
+      |> connect_mock()
+
       url = "http://proxy-tuple-atom.com"
-      :ok = Connections.open_conn(url, name, proxy: {'localhost', 9050})
+      :ok = Conn.open(url, name, proxy: {'localhost', 9050})
       conn = Connections.checkin(url, name)
 
       %Connections{
@@ -759,9 +549,12 @@ defmodule Pleroma.Pool.ConnectionsTest do
     end
 
     test "as ip and ssl", %{name: name} do
+      open_mock()
+      |> connect_mock()
+
       url = "https://proxy-string.com"
 
-      :ok = Connections.open_conn(url, name, proxy: {{127, 0, 0, 1}, 8123})
+      :ok = Conn.open(url, name, proxy: {{127, 0, 0, 1}, 8123})
       conn = Connections.checkin(url, name)
 
       %Connections{
@@ -779,8 +572,11 @@ defmodule Pleroma.Pool.ConnectionsTest do
     end
 
     test "as host and ssl", %{name: name} do
+      open_mock()
+      |> connect_mock()
+
       url = "https://proxy-tuple-atom.com"
-      :ok = Connections.open_conn(url, name, proxy: {'localhost', 9050})
+      :ok = Conn.open(url, name, proxy: {'localhost', 9050})
       conn = Connections.checkin(url, name)
 
       %Connections{
@@ -798,9 +594,11 @@ defmodule Pleroma.Pool.ConnectionsTest do
     end
 
     test "with socks type", %{name: name} do
+      open_mock()
+
       url = "http://proxy-socks.com"
 
-      :ok = Connections.open_conn(url, name, proxy: {:socks5, 'localhost', 1234})
+      :ok = Conn.open(url, name, proxy: {:socks5, 'localhost', 1234})
 
       conn = Connections.checkin(url, name)
 
@@ -819,9 +617,10 @@ defmodule Pleroma.Pool.ConnectionsTest do
     end
 
     test "with socks4 type and ssl", %{name: name} do
+      open_mock()
       url = "https://proxy-socks.com"
 
-      :ok = Connections.open_conn(url, name, proxy: {:socks4, 'localhost', 1234})
+      :ok = Conn.open(url, name, proxy: {:socks4, 'localhost', 1234})
 
       conn = Connections.checkin(url, name)
 
@@ -888,72 +687,74 @@ defmodule Pleroma.Pool.ConnectionsTest do
   end
 
   describe "get_unused_conns/1" do
-    test "crf is equalent, sorting by reference" do
-      conns = %{
-        "1" => %Conn{
-          conn_state: :idle,
-          last_reference: now() - 1
-        },
-        "2" => %Conn{
-          conn_state: :idle,
-          last_reference: now()
-        }
-      }
-
-      assert [{"1", _unused_conn} | _others] = Connections.get_unused_conns(conns)
+    test "crf is equalent, sorting by reference", %{name: name} do
+      Connections.add_conn(name, "1", %Conn{
+        conn_state: :idle,
+        last_reference: now() - 1
+      })
+
+      Connections.add_conn(name, "2", %Conn{
+        conn_state: :idle,
+        last_reference: now()
+      })
+
+      assert [{"1", _unused_conn} | _others] = Connections.get_unused_conns(name)
     end
 
-    test "reference is equalent, sorting by crf" do
-      conns = %{
-        "1" => %Conn{
-          conn_state: :idle,
-          crf: 1.999
-        },
-        "2" => %Conn{
-          conn_state: :idle,
-          crf: 2
-        }
-      }
+    test "reference is equalent, sorting by crf", %{name: name} do
+      Connections.add_conn(name, "1", %Conn{
+        conn_state: :idle,
+        crf: 1.999
+      })
+
+      Connections.add_conn(name, "2", %Conn{
+        conn_state: :idle,
+        crf: 2
+      })
 
-      assert [{"1", _unused_conn} | _others] = Connections.get_unused_conns(conns)
+      assert [{"1", _unused_conn} | _others] = Connections.get_unused_conns(name)
     end
 
-    test "higher crf and lower reference" do
-      conns = %{
-        "1" => %Conn{
-          conn_state: :idle,
-          crf: 3,
-          last_reference: now() - 1
-        },
-        "2" => %Conn{
-          conn_state: :idle,
-          crf: 2,
-          last_reference: now()
-        }
-      }
+    test "higher crf and lower reference", %{name: name} do
+      Connections.add_conn(name, "1", %Conn{
+        conn_state: :idle,
+        crf: 3,
+        last_reference: now() - 1
+      })
 
-      assert [{"2", _unused_conn} | _others] = Connections.get_unused_conns(conns)
+      Connections.add_conn(name, "2", %Conn{
+        conn_state: :idle,
+        crf: 2,
+        last_reference: now()
+      })
+
+      assert [{"2", _unused_conn} | _others] = Connections.get_unused_conns(name)
     end
 
-    test "lower crf and lower reference" do
-      conns = %{
-        "1" => %Conn{
-          conn_state: :idle,
-          crf: 1.99,
-          last_reference: now() - 1
-        },
-        "2" => %Conn{
-          conn_state: :idle,
-          crf: 2,
-          last_reference: now()
-        }
-      }
+    test "lower crf and lower reference", %{name: name} do
+      Connections.add_conn(name, "1", %Conn{
+        conn_state: :idle,
+        crf: 1.99,
+        last_reference: now() - 1
+      })
+
+      Connections.add_conn(name, "2", %Conn{
+        conn_state: :idle,
+        crf: 2,
+        last_reference: now()
+      })
 
-      assert [{"1", _unused_conn} | _others] = Connections.get_unused_conns(conns)
+      assert [{"1", _unused_conn} | _others] = Connections.get_unused_conns(name)
     end
   end
 
-  defp now do
-    :os.system_time(:second)
+  test "count/1" do
+    name = :test_count
+    {:ok, _} = Connections.start_link({name, [checkin_timeout: 150]})
+    assert Connections.count(name) == 0
+    Connections.add_conn(name, "1", %Conn{conn: self()})
+    assert Connections.count(name) == 1
+    Connections.remove_conn(name, "1")
+    assert Connections.count(name) == 0
   end
 end