Merge branch 'develop' into gun
[akkoma] / test / web / admin_api / admin_api_controller_test.exs
index d11b26207f18909767a4d79c04e340f5442824ca..d6b8399480b200b8ccc70b5bb4b5cb362a64cc05 100644 (file)
@@ -1,12 +1,17 @@
 # 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.Web.AdminAPI.AdminAPIControllerTest do
   use Pleroma.Web.ConnCase
   use Oban.Testing, repo: Pleroma.Repo
 
+  import Pleroma.Factory
+  import ExUnit.CaptureLog
+
   alias Pleroma.Activity
+  alias Pleroma.Config
+  alias Pleroma.ConfigDB
   alias Pleroma.HTML
   alias Pleroma.ModerationLog
   alias Pleroma.Repo
@@ -18,7 +23,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
   alias Pleroma.Web.CommonAPI
   alias Pleroma.Web.MastodonAPI.StatusView
   alias Pleroma.Web.MediaProxy
-  import Pleroma.Factory
 
   setup_all do
     Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
@@ -40,7 +44,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
   describe "with [:auth, :enforce_oauth_admin_scope_usage]," do
     clear_config([:auth, :enforce_oauth_admin_scope_usage]) do
-      Pleroma.Config.put([:auth, :enforce_oauth_admin_scope_usage], true)
+      Config.put([:auth, :enforce_oauth_admin_scope_usage], true)
     end
 
     test "GET /api/pleroma/admin/users/:nickname requires admin:read:accounts or broader scope",
@@ -90,7 +94,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
   describe "unless [:auth, :enforce_oauth_admin_scope_usage]," do
     clear_config([:auth, :enforce_oauth_admin_scope_usage]) do
-      Pleroma.Config.put([:auth, :enforce_oauth_admin_scope_usage], false)
+      Config.put([:auth, :enforce_oauth_admin_scope_usage], false)
     end
 
     test "GET /api/pleroma/admin/users/:nickname requires " <>
@@ -578,11 +582,11 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
   describe "POST /api/pleroma/admin/email_invite, with valid config" do
     clear_config([:instance, :registrations_open]) do
-      Pleroma.Config.put([:instance, :registrations_open], false)
+      Config.put([:instance, :registrations_open], false)
     end
 
     clear_config([:instance, :invites_enabled]) do
-      Pleroma.Config.put([:instance, :invites_enabled], true)
+      Config.put([:instance, :invites_enabled], true)
     end
 
     test "sends invitation and returns 204", %{admin: admin, conn: conn} do
@@ -597,12 +601,12 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
       assert json_response(conn, :no_content)
 
-      token_record = List.last(Pleroma.Repo.all(Pleroma.UserInviteToken))
+      token_record = List.last(Repo.all(Pleroma.UserInviteToken))
       assert token_record
       refute token_record.used
 
-      notify_email = Pleroma.Config.get([:instance, :notify_email])
-      instance_name = Pleroma.Config.get([:instance, :name])
+      notify_email = Config.get([:instance, :notify_email])
+      instance_name = Config.get([:instance, :name])
 
       email =
         Pleroma.Emails.UserEmail.user_invitation_email(
@@ -638,8 +642,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
     clear_config([:instance, :invites_enabled])
 
     test "it returns 500 if `invites_enabled` is not enabled", %{conn: conn} do
-      Pleroma.Config.put([:instance, :registrations_open], false)
-      Pleroma.Config.put([:instance, :invites_enabled], false)
+      Config.put([:instance, :registrations_open], false)
+      Config.put([:instance, :invites_enabled], false)
 
       conn = post(conn, "/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD")
 
@@ -647,8 +651,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
     end
 
     test "it returns 500 if `registrations_open` is enabled", %{conn: conn} do
-      Pleroma.Config.put([:instance, :registrations_open], true)
-      Pleroma.Config.put([:instance, :invites_enabled], true)
+      Config.put([:instance, :registrations_open], true)
+      Config.put([:instance, :invites_enabled], true)
 
       conn = post(conn, "/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD")
 
@@ -1363,9 +1367,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
       }
     end
 
-    test "requires write:reports scope", %{conn: conn, id: id, admin: admin} do
-      read_token = insert(:oauth_token, user: admin, scopes: ["read"])
-      write_token = insert(:oauth_token, user: admin, scopes: ["write:reports"])
+    test "requires admin:write:reports scope", %{conn: conn, id: id, admin: admin} do
+      read_token = insert(:oauth_token, user: admin, scopes: ["admin:read"])
+      write_token = insert(:oauth_token, user: admin, scopes: ["admin:write:reports"])
 
       response =
         conn
@@ -1376,7 +1380,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
         |> json_response(403)
 
       assert response == %{
-               "error" => "Insufficient permissions: admin:write:reports | write:reports."
+               "error" => "Insufficient permissions: admin:write:reports."
              }
 
       conn
@@ -1876,33 +1880,41 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                "@#{admin.nickname} deleted status ##{id}"
     end
 
-    test "returns error when status is not exist", %{conn: conn} do
+    test "returns 404 when the status does not exist", %{conn: conn} do
       conn = delete(conn, "/api/pleroma/admin/statuses/test")
 
-      assert json_response(conn, :bad_request) == "Could not delete"
+      assert json_response(conn, :not_found) == "Not found"
     end
   end
 
   describe "GET /api/pleroma/admin/config" do
-    test "without any settings in db", %{conn: conn} do
+    clear_config(:configurable_from_database) do
+      Config.put(:configurable_from_database, true)
+    end
+
+    test "when configuration from database is off", %{conn: conn} do
+      Config.put(:configurable_from_database, false)
       conn = get(conn, "/api/pleroma/admin/config")
 
-      assert json_response(conn, 200) == %{"configs" => []}
+      assert json_response(conn, 400) ==
+               "To use this endpoint you need to enable configuration from database."
     end
 
-    test "with settings in db", %{conn: conn} do
+    test "with settings only in db", %{conn: conn} do
       config1 = insert(:config)
       config2 = insert(:config)
 
-      conn = get(conn, "/api/pleroma/admin/config")
+      conn = get(conn, "/api/pleroma/admin/config", %{"only_db" => true})
 
       %{
         "configs" => [
           %{
+            "group" => ":pleroma",
             "key" => key1,
             "value" => _
           },
           %{
+            "group" => ":pleroma",
             "key" => key2,
             "value" => _
           }
@@ -1912,11 +1924,107 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
       assert key1 == config1.key
       assert key2 == config2.key
     end
+
+    test "db is added to settings that are in db", %{conn: conn} do
+      _config = insert(:config, key: ":instance", value: ConfigDB.to_binary(name: "Some name"))
+
+      %{"configs" => configs} =
+        conn
+        |> get("/api/pleroma/admin/config")
+        |> json_response(200)
+
+      [instance_config] =
+        Enum.filter(configs, fn %{"group" => group, "key" => key} ->
+          group == ":pleroma" and key == ":instance"
+        end)
+
+      assert instance_config["db"] == [":name"]
+    end
+
+    test "merged default setting with db settings", %{conn: conn} do
+      config1 = insert(:config)
+      config2 = insert(:config)
+
+      config3 =
+        insert(:config,
+          value: ConfigDB.to_binary(k1: :v1, k2: :v2)
+        )
+
+      %{"configs" => configs} =
+        conn
+        |> get("/api/pleroma/admin/config")
+        |> json_response(200)
+
+      assert length(configs) > 3
+
+      received_configs =
+        Enum.filter(configs, fn %{"group" => group, "key" => key} ->
+          group == ":pleroma" and key in [config1.key, config2.key, config3.key]
+        end)
+
+      assert length(received_configs) == 3
+
+      db_keys =
+        config3.value
+        |> ConfigDB.from_binary()
+        |> Keyword.keys()
+        |> ConfigDB.convert()
+
+      Enum.each(received_configs, fn %{"value" => value, "db" => db} ->
+        assert db in [[config1.key], [config2.key], db_keys]
+
+        assert value in [
+                 ConfigDB.from_binary_with_convert(config1.value),
+                 ConfigDB.from_binary_with_convert(config2.value),
+                 ConfigDB.from_binary_with_convert(config3.value)
+               ]
+      end)
+    end
+
+    test "subkeys with full update right merge", %{conn: conn} do
+      config1 =
+        insert(:config,
+          key: ":emoji",
+          value: ConfigDB.to_binary(groups: [a: 1, b: 2], key: [a: 1])
+        )
+
+      config2 =
+        insert(:config,
+          key: ":assets",
+          value: ConfigDB.to_binary(mascots: [a: 1, b: 2], key: [a: 1])
+        )
+
+      %{"configs" => configs} =
+        conn
+        |> get("/api/pleroma/admin/config")
+        |> json_response(200)
+
+      vals =
+        Enum.filter(configs, fn %{"group" => group, "key" => key} ->
+          group == ":pleroma" and key in [config1.key, config2.key]
+        end)
+
+      emoji = Enum.find(vals, fn %{"key" => key} -> key == ":emoji" end)
+      assets = Enum.find(vals, fn %{"key" => key} -> key == ":assets" end)
+
+      emoji_val = ConfigDB.transform_with_out_binary(emoji["value"])
+      assets_val = ConfigDB.transform_with_out_binary(assets["value"])
+
+      assert emoji_val[:groups] == [a: 1, b: 2]
+      assert assets_val[:mascots] == [a: 1, b: 2]
+    end
+  end
+
+  test "POST /api/pleroma/admin/config error", %{conn: conn} do
+    conn = post(conn, "/api/pleroma/admin/config", %{"configs" => []})
+
+    assert json_response(conn, 400) ==
+             "To use this endpoint you need to enable configuration from database."
   end
 
   describe "POST /api/pleroma/admin/config" do
     setup do
-      temp_file = "config/test.exported_from_db.secret.exs"
+      http = Application.get_env(:pleroma, :http)
 
       on_exit(fn ->
         Application.delete_env(:pleroma, :key1)
@@ -1927,28 +2035,33 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
         Application.delete_env(:pleroma, :keyaa2)
         Application.delete_env(:pleroma, Pleroma.Web.Endpoint.NotReal)
         Application.delete_env(:pleroma, Pleroma.Captcha.NotReal)
-        :ok = File.rm(temp_file)
+        Application.put_env(:pleroma, :http, http)
+        Application.put_env(:tesla, :adapter, Tesla.Mock)
+        Restarter.Pleroma.refresh()
       end)
     end
 
-    clear_config([:instance, :dynamic_configuration]) do
-      Pleroma.Config.put([:instance, :dynamic_configuration], true)
+    clear_config(:configurable_from_database) do
+      Config.put(:configurable_from_database, true)
     end
 
     @tag capture_log: true
     test "create new config setting in db", %{conn: conn} do
+      ueberauth = Application.get_env(:ueberauth, Ueberauth)
+      on_exit(fn -> Application.put_env(:ueberauth, Ueberauth, ueberauth) end)
+
       conn =
         post(conn, "/api/pleroma/admin/config", %{
           configs: [
-            %{group: "pleroma", key: "key1", value: "value1"},
+            %{group: ":pleroma", key: ":key1", value: "value1"},
             %{
-              group: "ueberauth",
-              key: "Ueberauth.Strategy.Twitter.OAuth",
+              group: ":ueberauth",
+              key: "Ueberauth",
               value: [%{"tuple" => [":consumer_secret", "aaaa"]}]
             },
             %{
-              group: "pleroma",
-              key: "key2",
+              group: ":pleroma",
+              key: ":key2",
               value: %{
                 ":nested_1" => "nested_value1",
                 ":nested_2" => [
@@ -1958,21 +2071,21 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
               }
             },
             %{
-              group: "pleroma",
-              key: "key3",
+              group: ":pleroma",
+              key: ":key3",
               value: [
                 %{"nested_3" => ":nested_3", "nested_33" => "nested_33"},
                 %{"nested_4" => true}
               ]
             },
             %{
-              group: "pleroma",
-              key: "key4",
+              group: ":pleroma",
+              key: ":key4",
               value: %{":nested_5" => ":upload", "endpoint" => "https://example.com"}
             },
             %{
-              group: "idna",
-              key: "key5",
+              group: ":idna",
+              key: ":key5",
               value: %{"tuple" => ["string", "Pleroma.Captcha.NotReal", []]}
             }
           ]
@@ -1981,43 +2094,49 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
       assert json_response(conn, 200) == %{
                "configs" => [
                  %{
-                   "group" => "pleroma",
-                   "key" => "key1",
-                   "value" => "value1"
+                   "group" => ":pleroma",
+                   "key" => ":key1",
+                   "value" => "value1",
+                   "db" => [":key1"]
                  },
                  %{
-                   "group" => "ueberauth",
-                   "key" => "Ueberauth.Strategy.Twitter.OAuth",
-                   "value" => [%{"tuple" => [":consumer_secret", "aaaa"]}]
+                   "group" => ":ueberauth",
+                   "key" => "Ueberauth",
+                   "value" => [%{"tuple" => [":consumer_secret", "aaaa"]}],
+                   "db" => [":consumer_secret"]
                  },
                  %{
-                   "group" => "pleroma",
-                   "key" => "key2",
+                   "group" => ":pleroma",
+                   "key" => ":key2",
                    "value" => %{
                      ":nested_1" => "nested_value1",
                      ":nested_2" => [
                        %{":nested_22" => "nested_value222"},
                        %{":nested_33" => %{":nested_44" => "nested_444"}}
                      ]
-                   }
+                   },
+                   "db" => [":key2"]
                  },
                  %{
-                   "group" => "pleroma",
-                   "key" => "key3",
+                   "group" => ":pleroma",
+                   "key" => ":key3",
                    "value" => [
                      %{"nested_3" => ":nested_3", "nested_33" => "nested_33"},
                      %{"nested_4" => true}
-                   ]
+                   ],
+                   "db" => [":key3"]
                  },
                  %{
-                   "group" => "pleroma",
-                   "key" => "key4",
-                   "value" => %{"endpoint" => "https://example.com", ":nested_5" => ":upload"}
+                   "group" => ":pleroma",
+                   "key" => ":key4",
+                   "value" => %{"endpoint" => "https://example.com", ":nested_5" => ":upload"},
+                   "db" => [":key4"]
                  },
                  %{
-                   "group" => "idna",
-                   "key" => "key5",
-                   "value" => %{"tuple" => ["string", "Pleroma.Captcha.NotReal", []]}
+                   "group" => ":idna",
+                   "key" => ":key5",
+                   "value" => %{"tuple" => ["string", "Pleroma.Captcha.NotReal", []]},
+                   "db" => [":key5"]
                  }
                ]
              }
@@ -2045,25 +2164,34 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
       assert Application.get_env(:idna, :key5) == {"string", Pleroma.Captcha.NotReal, []}
     end
 
-    test "update config setting & delete", %{conn: conn} do
-      config1 = insert(:config, key: "keyaa1")
-      config2 = insert(:config, key: "keyaa2")
+    test "save configs setting without explicit key", %{conn: conn} do
+      level = Application.get_env(:quack, :level)
+      meta = Application.get_env(:quack, :meta)
+      webhook_url = Application.get_env(:quack, :webhook_url)
 
-      insert(:config,
-        group: "ueberauth",
-        key: "Ueberauth.Strategy.Microsoft.OAuth",
-        value: :erlang.term_to_binary([])
-      )
+      on_exit(fn ->
+        Application.put_env(:quack, :level, level)
+        Application.put_env(:quack, :meta, meta)
+        Application.put_env(:quack, :webhook_url, webhook_url)
+      end)
 
       conn =
         post(conn, "/api/pleroma/admin/config", %{
           configs: [
-            %{group: config1.group, key: config1.key, value: "another_value"},
-            %{group: config2.group, key: config2.key, delete: "true"},
             %{
-              group: "ueberauth",
-              key: "Ueberauth.Strategy.Microsoft.OAuth",
-              delete: "true"
+              group: ":quack",
+              key: ":level",
+              value: ":info"
+            },
+            %{
+              group: ":quack",
+              key: ":meta",
+              value: [":none"]
+            },
+            %{
+              group: ":quack",
+              key: ":webhook_url",
+              value: "https://hooks.slack.com/services/KEY"
             }
           ]
         })
@@ -2071,23 +2199,401 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
       assert json_response(conn, 200) == %{
                "configs" => [
                  %{
-                   "group" => "pleroma",
+                   "group" => ":quack",
+                   "key" => ":level",
+                   "value" => ":info",
+                   "db" => [":level"]
+                 },
+                 %{
+                   "group" => ":quack",
+                   "key" => ":meta",
+                   "value" => [":none"],
+                   "db" => [":meta"]
+                 },
+                 %{
+                   "group" => ":quack",
+                   "key" => ":webhook_url",
+                   "value" => "https://hooks.slack.com/services/KEY",
+                   "db" => [":webhook_url"]
+                 }
+               ]
+             }
+
+      assert Application.get_env(:quack, :level) == :info
+      assert Application.get_env(:quack, :meta) == [:none]
+      assert Application.get_env(:quack, :webhook_url) == "https://hooks.slack.com/services/KEY"
+    end
+
+    test "saving config with partial update", %{conn: conn} do
+      config = insert(:config, key: ":key1", value: :erlang.term_to_binary(key1: 1, key2: 2))
+
+      conn =
+        post(conn, "/api/pleroma/admin/config", %{
+          configs: [
+            %{group: config.group, key: config.key, value: [%{"tuple" => [":key3", 3]}]}
+          ]
+        })
+
+      assert json_response(conn, 200) == %{
+               "configs" => [
+                 %{
+                   "group" => ":pleroma",
+                   "key" => ":key1",
+                   "value" => [
+                     %{"tuple" => [":key1", 1]},
+                     %{"tuple" => [":key2", 2]},
+                     %{"tuple" => [":key3", 3]}
+                   ],
+                   "db" => [":key1", ":key2", ":key3"]
+                 }
+               ]
+             }
+    end
+
+    test "saving config which need pleroma reboot", %{conn: conn} do
+      chat = Config.get(:chat)
+      on_exit(fn -> Config.put(:chat, chat) end)
+
+      assert post(
+               conn,
+               "/api/pleroma/admin/config",
+               %{
+                 configs: [
+                   %{group: ":pleroma", key: ":chat", value: [%{"tuple" => [":enabled", true]}]}
+                 ]
+               }
+             )
+             |> json_response(200) == %{
+               "configs" => [
+                 %{
+                   "db" => [":enabled"],
+                   "group" => ":pleroma",
+                   "key" => ":chat",
+                   "value" => [%{"tuple" => [":enabled", true]}]
+                 }
+               ],
+               "need_reboot" => true
+             }
+
+      configs =
+        conn
+        |> get("/api/pleroma/admin/config")
+        |> json_response(200)
+
+      assert configs["need_reboot"]
+
+      capture_log(fn ->
+        assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) == %{}
+      end) =~ "pleroma restarted"
+
+      configs =
+        conn
+        |> get("/api/pleroma/admin/config")
+        |> json_response(200)
+
+      refute Map.has_key?(configs, "need_reboot")
+    end
+
+    test "update setting which need reboot, don't change reboot flag until reboot", %{conn: conn} do
+      chat = Config.get(:chat)
+      on_exit(fn -> Config.put(:chat, chat) end)
+
+      assert post(
+               conn,
+               "/api/pleroma/admin/config",
+               %{
+                 configs: [
+                   %{group: ":pleroma", key: ":chat", value: [%{"tuple" => [":enabled", true]}]}
+                 ]
+               }
+             )
+             |> json_response(200) == %{
+               "configs" => [
+                 %{
+                   "db" => [":enabled"],
+                   "group" => ":pleroma",
+                   "key" => ":chat",
+                   "value" => [%{"tuple" => [":enabled", true]}]
+                 }
+               ],
+               "need_reboot" => true
+             }
+
+      assert post(conn, "/api/pleroma/admin/config", %{
+               configs: [
+                 %{group: ":pleroma", key: ":key1", value: [%{"tuple" => [":key3", 3]}]}
+               ]
+             })
+             |> json_response(200) == %{
+               "configs" => [
+                 %{
+                   "group" => ":pleroma",
+                   "key" => ":key1",
+                   "value" => [
+                     %{"tuple" => [":key3", 3]}
+                   ],
+                   "db" => [":key3"]
+                 }
+               ],
+               "need_reboot" => true
+             }
+
+      capture_log(fn ->
+        assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) == %{}
+      end) =~ "pleroma restarted"
+
+      configs =
+        conn
+        |> get("/api/pleroma/admin/config")
+        |> json_response(200)
+
+      refute Map.has_key?(configs, "need_reboot")
+    end
+
+    test "saving config with nested merge", %{conn: conn} do
+      config =
+        insert(:config, key: ":key1", value: :erlang.term_to_binary(key1: 1, key2: [k1: 1, k2: 2]))
+
+      conn =
+        post(conn, "/api/pleroma/admin/config", %{
+          configs: [
+            %{
+              group: config.group,
+              key: config.key,
+              value: [
+                %{"tuple" => [":key3", 3]},
+                %{
+                  "tuple" => [
+                    ":key2",
+                    [
+                      %{"tuple" => [":k2", 1]},
+                      %{"tuple" => [":k3", 3]}
+                    ]
+                  ]
+                }
+              ]
+            }
+          ]
+        })
+
+      assert json_response(conn, 200) == %{
+               "configs" => [
+                 %{
+                   "group" => ":pleroma",
+                   "key" => ":key1",
+                   "value" => [
+                     %{"tuple" => [":key1", 1]},
+                     %{"tuple" => [":key3", 3]},
+                     %{
+                       "tuple" => [
+                         ":key2",
+                         [
+                           %{"tuple" => [":k1", 1]},
+                           %{"tuple" => [":k2", 1]},
+                           %{"tuple" => [":k3", 3]}
+                         ]
+                       ]
+                     }
+                   ],
+                   "db" => [":key1", ":key3", ":key2"]
+                 }
+               ]
+             }
+    end
+
+    test "saving special atoms", %{conn: conn} do
+      conn =
+        post(conn, "/api/pleroma/admin/config", %{
+          "configs" => [
+            %{
+              "group" => ":pleroma",
+              "key" => ":key1",
+              "value" => [
+                %{
+                  "tuple" => [
+                    ":ssl_options",
+                    [%{"tuple" => [":versions", [":tlsv1", ":tlsv1.1", ":tlsv1.2"]]}]
+                  ]
+                }
+              ]
+            }
+          ]
+        })
+
+      assert json_response(conn, 200) == %{
+               "configs" => [
+                 %{
+                   "group" => ":pleroma",
+                   "key" => ":key1",
+                   "value" => [
+                     %{
+                       "tuple" => [
+                         ":ssl_options",
+                         [%{"tuple" => [":versions", [":tlsv1", ":tlsv1.1", ":tlsv1.2"]]}]
+                       ]
+                     }
+                   ],
+                   "db" => [":ssl_options"]
+                 }
+               ]
+             }
+
+      assert Application.get_env(:pleroma, :key1) == [
+               ssl_options: [versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"]]
+             ]
+    end
+
+    test "saving full setting if value is in full_key_update list", %{conn: conn} do
+      backends = Application.get_env(:logger, :backends)
+      on_exit(fn -> Application.put_env(:logger, :backends, backends) end)
+
+      config =
+        insert(:config,
+          group: ":logger",
+          key: ":backends",
+          value: :erlang.term_to_binary([])
+        )
+
+      conn =
+        post(conn, "/api/pleroma/admin/config", %{
+          configs: [
+            %{
+              group: config.group,
+              key: config.key,
+              value: [":console", %{"tuple" => ["ExSyslogger", ":ex_syslogger"]}]
+            }
+          ]
+        })
+
+      assert json_response(conn, 200) == %{
+               "configs" => [
+                 %{
+                   "group" => ":logger",
+                   "key" => ":backends",
+                   "value" => [
+                     ":console",
+                     %{"tuple" => ["ExSyslogger", ":ex_syslogger"]}
+                   ],
+                   "db" => [":backends"]
+                 }
+               ]
+             }
+
+      assert Application.get_env(:logger, :backends) == [
+               :console,
+               {ExSyslogger, :ex_syslogger}
+             ]
+
+      capture_log(fn ->
+        require Logger
+        Logger.warn("Ooops...")
+      end) =~ "Ooops..."
+    end
+
+    test "saving full setting if value is not keyword", %{conn: conn} do
+      config =
+        insert(:config,
+          group: ":tesla",
+          key: ":adapter",
+          value: :erlang.term_to_binary(Tesla.Adapter.Hackey)
+        )
+
+      conn =
+        post(conn, "/api/pleroma/admin/config", %{
+          configs: [
+            %{group: config.group, key: config.key, value: "Tesla.Adapter.Httpc"}
+          ]
+        })
+
+      assert json_response(conn, 200) == %{
+               "configs" => [
+                 %{
+                   "group" => ":tesla",
+                   "key" => ":adapter",
+                   "value" => "Tesla.Adapter.Httpc",
+                   "db" => [":adapter"]
+                 }
+               ],
+               "need_reboot" => true
+             }
+    end
+
+    test "update config setting & delete with fallback to default value", %{
+      conn: conn,
+      admin: admin,
+      token: token
+    } do
+      ueberauth = Application.get_env(:ueberauth, Ueberauth)
+      config1 = insert(:config, key: ":keyaa1")
+      config2 = insert(:config, key: ":keyaa2")
+
+      config3 =
+        insert(:config,
+          group: ":ueberauth",
+          key: "Ueberauth"
+        )
+
+      conn =
+        post(conn, "/api/pleroma/admin/config", %{
+          configs: [
+            %{group: config1.group, key: config1.key, value: "another_value"},
+            %{group: config2.group, key: config2.key, value: "another_value"}
+          ]
+        })
+
+      assert json_response(conn, 200) == %{
+               "configs" => [
+                 %{
+                   "group" => ":pleroma",
                    "key" => config1.key,
-                   "value" => "another_value"
+                   "value" => "another_value",
+                   "db" => [":keyaa1"]
+                 },
+                 %{
+                   "group" => ":pleroma",
+                   "key" => config2.key,
+                   "value" => "another_value",
+                   "db" => [":keyaa2"]
                  }
                ]
              }
 
       assert Application.get_env(:pleroma, :keyaa1) == "another_value"
-      refute Application.get_env(:pleroma, :keyaa2)
+      assert Application.get_env(:pleroma, :keyaa2) == "another_value"
+      assert Application.get_env(:ueberauth, Ueberauth) == ConfigDB.from_binary(config3.value)
+
+      conn =
+        build_conn()
+        |> assign(:user, admin)
+        |> assign(:token, token)
+        |> post("/api/pleroma/admin/config", %{
+          configs: [
+            %{group: config2.group, key: config2.key, delete: true},
+            %{
+              group: ":ueberauth",
+              key: "Ueberauth",
+              delete: true
+            }
+          ]
+        })
+
+      assert json_response(conn, 200) == %{
+               "configs" => []
+             }
+
+      assert Application.get_env(:ueberauth, Ueberauth) == ueberauth
+      refute Keyword.has_key?(Application.get_all_env(:pleroma), :keyaa2)
     end
 
     test "common config example", %{conn: conn} do
+      adapter = Application.get_env(:tesla, :adapter)
+      on_exit(fn -> Application.put_env(:tesla, :adapter, adapter) end)
+
       conn =
         post(conn, "/api/pleroma/admin/config", %{
           configs: [
             %{
-              "group" => "pleroma",
+              "group" => ":pleroma",
               "key" => "Pleroma.Captcha.NotReal",
               "value" => [
                 %{"tuple" => [":enabled", false]},
@@ -2095,20 +2601,28 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                 %{"tuple" => [":seconds_valid", 60]},
                 %{"tuple" => [":path", ""]},
                 %{"tuple" => [":key1", nil]},
-                %{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]},
                 %{"tuple" => [":regex1", "~r/https:\/\/example.com/"]},
                 %{"tuple" => [":regex2", "~r/https:\/\/example.com/u"]},
                 %{"tuple" => [":regex3", "~r/https:\/\/example.com/i"]},
-                %{"tuple" => [":regex4", "~r/https:\/\/example.com/s"]}
+                %{"tuple" => [":regex4", "~r/https:\/\/example.com/s"]},
+                %{"tuple" => [":name", "Pleroma"]}
               ]
+            },
+            %{
+              "group" => ":tesla",
+              "key" => ":adapter",
+              "value" => "Tesla.Adapter.Httpc"
             }
           ]
         })
 
+      assert Application.get_env(:tesla, :adapter) == Tesla.Adapter.Httpc
+      assert Config.get([Pleroma.Captcha.NotReal, :name]) == "Pleroma"
+
       assert json_response(conn, 200) == %{
                "configs" => [
                  %{
-                   "group" => "pleroma",
+                   "group" => ":pleroma",
                    "key" => "Pleroma.Captcha.NotReal",
                    "value" => [
                      %{"tuple" => [":enabled", false]},
@@ -2116,14 +2630,33 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                      %{"tuple" => [":seconds_valid", 60]},
                      %{"tuple" => [":path", ""]},
                      %{"tuple" => [":key1", nil]},
-                     %{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]},
                      %{"tuple" => [":regex1", "~r/https:\\/\\/example.com/"]},
                      %{"tuple" => [":regex2", "~r/https:\\/\\/example.com/u"]},
                      %{"tuple" => [":regex3", "~r/https:\\/\\/example.com/i"]},
-                     %{"tuple" => [":regex4", "~r/https:\\/\\/example.com/s"]}
+                     %{"tuple" => [":regex4", "~r/https:\\/\\/example.com/s"]},
+                     %{"tuple" => [":name", "Pleroma"]}
+                   ],
+                   "db" => [
+                     ":enabled",
+                     ":method",
+                     ":seconds_valid",
+                     ":path",
+                     ":key1",
+                     ":regex1",
+                     ":regex2",
+                     ":regex3",
+                     ":regex4",
+                     ":name"
                    ]
+                 },
+                 %{
+                   "group" => ":tesla",
+                   "key" => ":adapter",
+                   "value" => "Tesla.Adapter.Httpc",
+                   "db" => [":adapter"]
                  }
-               ]
+               ],
+               "need_reboot" => true
              }
     end
 
@@ -2132,7 +2665,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
         post(conn, "/api/pleroma/admin/config", %{
           configs: [
             %{
-              "group" => "pleroma",
+              "group" => ":pleroma",
               "key" => "Pleroma.Web.Endpoint.NotReal",
               "value" => [
                 %{
@@ -2196,7 +2729,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
       assert json_response(conn, 200) == %{
                "configs" => [
                  %{
-                   "group" => "pleroma",
+                   "group" => ":pleroma",
                    "key" => "Pleroma.Web.Endpoint.NotReal",
                    "value" => [
                      %{
@@ -2252,7 +2785,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                          ]
                        ]
                      }
-                   ]
+                   ],
+                   "db" => [":http"]
                  }
                ]
              }
@@ -2263,7 +2797,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
         post(conn, "/api/pleroma/admin/config", %{
           configs: [
             %{
-              "group" => "pleroma",
+              "group" => ":pleroma",
               "key" => ":key1",
               "value" => [
                 %{"tuple" => [":key2", "some_val"]},
@@ -2293,7 +2827,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                %{
                  "configs" => [
                    %{
-                     "group" => "pleroma",
+                     "group" => ":pleroma",
                      "key" => ":key1",
                      "value" => [
                        %{"tuple" => [":key2", "some_val"]},
@@ -2314,7 +2848,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                            }
                          ]
                        }
-                     ]
+                     ],
+                     "db" => [":key2", ":key3"]
                    }
                  ]
                }
@@ -2325,7 +2860,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
         post(conn, "/api/pleroma/admin/config", %{
           configs: [
             %{
-              "group" => "pleroma",
+              "group" => ":pleroma",
               "key" => ":key1",
               "value" => %{"key" => "some_val"}
             }
@@ -2336,83 +2871,21 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                %{
                  "configs" => [
                    %{
-                     "group" => "pleroma",
+                     "group" => ":pleroma",
                      "key" => ":key1",
-                     "value" => %{"key" => "some_val"}
+                     "value" => %{"key" => "some_val"},
+                     "db" => [":key1"]
                    }
                  ]
                }
     end
 
-    test "dispatch setting", %{conn: conn} do
-      conn =
-        post(conn, "/api/pleroma/admin/config", %{
-          configs: [
-            %{
-              "group" => "pleroma",
-              "key" => "Pleroma.Web.Endpoint.NotReal",
-              "value" => [
-                %{
-                  "tuple" => [
-                    ":http",
-                    [
-                      %{"tuple" => [":ip", %{"tuple" => [127, 0, 0, 1]}]},
-                      %{"tuple" => [":dispatch", ["{:_,
-       [
-         {\"/api/v1/streaming\", Pleroma.Web.MastodonAPI.WebsocketHandler, []},
-         {\"/websocket\", Phoenix.Endpoint.CowboyWebSocket,
-          {Phoenix.Transports.WebSocket,
-           {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, [path: \"/websocket\"]}}},
-         {:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}}
-       ]}"]]}
-                    ]
-                  ]
-                }
-              ]
-            }
-          ]
-        })
-
-      dispatch_string =
-        "{:_, [{\"/api/v1/streaming\", Pleroma.Web.MastodonAPI.WebsocketHandler, []}, " <>
-          "{\"/websocket\", Phoenix.Endpoint.CowboyWebSocket, {Phoenix.Transports.WebSocket, " <>
-          "{Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, [path: \"/websocket\"]}}}, " <>
-          "{:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}}]}"
-
-      assert json_response(conn, 200) == %{
-               "configs" => [
-                 %{
-                   "group" => "pleroma",
-                   "key" => "Pleroma.Web.Endpoint.NotReal",
-                   "value" => [
-                     %{
-                       "tuple" => [
-                         ":http",
-                         [
-                           %{"tuple" => [":ip", %{"tuple" => [127, 0, 0, 1]}]},
-                           %{
-                             "tuple" => [
-                               ":dispatch",
-                               [
-                                 dispatch_string
-                               ]
-                             ]
-                           }
-                         ]
-                       ]
-                     }
-                   ]
-                 }
-               ]
-             }
-    end
-
     test "queues key as atom", %{conn: conn} do
       conn =
         post(conn, "/api/pleroma/admin/config", %{
           configs: [
             %{
-              "group" => "oban",
+              "group" => ":oban",
               "key" => ":queues",
               "value" => [
                 %{"tuple" => [":federator_incoming", 50]},
@@ -2430,7 +2903,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
       assert json_response(conn, 200) == %{
                "configs" => [
                  %{
-                   "group" => "oban",
+                   "group" => ":oban",
                    "key" => ":queues",
                    "value" => [
                      %{"tuple" => [":federator_incoming", 50]},
@@ -2440,6 +2913,15 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                      %{"tuple" => [":transmogrifier", 20]},
                      %{"tuple" => [":scheduled_activities", 10]},
                      %{"tuple" => [":background", 5]}
+                   ],
+                   "db" => [
+                     ":federator_incoming",
+                     ":federator_outgoing",
+                     ":web_push",
+                     ":mailer",
+                     ":transmogrifier",
+                     ":scheduled_activities",
+                     ":background"
                    ]
                  }
                ]
@@ -2449,7 +2931,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
     test "delete part of settings by atom subkeys", %{conn: conn} do
       config =
         insert(:config,
-          key: "keyaa1",
+          key: ":keyaa1",
           value: :erlang.term_to_binary(subkey1: "val1", subkey2: "val2", subkey3: "val3")
         )
 
@@ -2460,57 +2942,171 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
               group: config.group,
               key: config.key,
               subkeys: [":subkey1", ":subkey3"],
-              delete: "true"
+              delete: true
             }
           ]
         })
 
-      assert(
-        json_response(conn, 200) == %{
-          "configs" => [
+      assert json_response(conn, 200) == %{
+               "configs" => [
+                 %{
+                   "group" => ":pleroma",
+                   "key" => ":keyaa1",
+                   "value" => [%{"tuple" => [":subkey2", "val2"]}],
+                   "db" => [":subkey2"]
+                 }
+               ]
+             }
+    end
+
+    test "proxy tuple localhost", %{conn: conn} do
+      conn =
+        post(conn, "/api/pleroma/admin/config", %{
+          configs: [
             %{
-              "group" => "pleroma",
-              "key" => "keyaa1",
-              "value" => [%{"tuple" => [":subkey2", "val2"]}]
+              group: ":pleroma",
+              key: ":http",
+              value: [
+                %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "localhost", 1234]}]},
+                %{"tuple" => [":send_user_agent", false]}
+              ]
             }
           ]
-        }
-      )
+        })
+
+      assert json_response(conn, 200) == %{
+               "configs" => [
+                 %{
+                   "group" => ":pleroma",
+                   "key" => ":http",
+                   "value" => [
+                     %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "localhost", 1234]}]},
+                     %{"tuple" => [":send_user_agent", false]}
+                   ],
+                   "db" => [":proxy_url", ":send_user_agent"]
+                 }
+               ]
+             }
     end
-  end
 
-  describe "config mix tasks run" do
-    setup do
-      temp_file = "config/test.exported_from_db.secret.exs"
+    test "proxy tuple domain", %{conn: conn} do
+      conn =
+        post(conn, "/api/pleroma/admin/config", %{
+          configs: [
+            %{
+              group: ":pleroma",
+              key: ":http",
+              value: [
+                %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "domain.com", 1234]}]},
+                %{"tuple" => [":send_user_agent", false]}
+              ]
+            }
+          ]
+        })
+
+      assert json_response(conn, 200) == %{
+               "configs" => [
+                 %{
+                   "group" => ":pleroma",
+                   "key" => ":http",
+                   "value" => [
+                     %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "domain.com", 1234]}]},
+                     %{"tuple" => [":send_user_agent", false]}
+                   ],
+                   "db" => [":proxy_url", ":send_user_agent"]
+                 }
+               ]
+             }
+    end
 
-      Mix.shell(Mix.Shell.Quiet)
+    test "proxy tuple ip", %{conn: conn} do
+      conn =
+        post(conn, "/api/pleroma/admin/config", %{
+          configs: [
+            %{
+              group: ":pleroma",
+              key: ":http",
+              value: [
+                %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "127.0.0.1", 1234]}]},
+                %{"tuple" => [":send_user_agent", false]}
+              ]
+            }
+          ]
+        })
 
-      on_exit(fn ->
-        Mix.shell(Mix.Shell.IO)
-        :ok = File.rm(temp_file)
-      end)
+      assert json_response(conn, 200) == %{
+               "configs" => [
+                 %{
+                   "group" => ":pleroma",
+                   "key" => ":http",
+                   "value" => [
+                     %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "127.0.0.1", 1234]}]},
+                     %{"tuple" => [":send_user_agent", false]}
+                   ],
+                   "db" => [":proxy_url", ":send_user_agent"]
+                 }
+               ]
+             }
+    end
+  end
 
-      :ok
+  describe "GET /api/pleroma/admin/restart" do
+    clear_config(:configurable_from_database) do
+      Config.put(:configurable_from_database, true)
     end
 
-    clear_config([:instance, :dynamic_configuration]) do
-      Pleroma.Config.put([:instance, :dynamic_configuration], true)
+    test "pleroma restarts", %{conn: conn} do
+      capture_log(fn ->
+        assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) == %{}
+      end) =~ "pleroma restarted"
+
+      refute Restarter.Pleroma.need_reboot?()
     end
+  end
+
+  describe "GET /api/pleroma/admin/statuses" do
+    test "returns all public, unlisted, and direct statuses", %{conn: conn, admin: admin} do
+      blocked = insert(:user)
+      user = insert(:user)
+      User.block(admin, blocked)
+
+      {:ok, _} =
+        CommonAPI.post(user, %{"status" => "@#{admin.nickname}", "visibility" => "direct"})
+
+      {:ok, _} = CommonAPI.post(user, %{"status" => ".", "visibility" => "unlisted"})
+      {:ok, _} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
+      {:ok, _} = CommonAPI.post(user, %{"status" => ".", "visibility" => "public"})
+      {:ok, _} = CommonAPI.post(blocked, %{"status" => ".", "visibility" => "public"})
 
-    clear_config([:feed, :post_title]) do
-      Pleroma.Config.put([:feed, :post_title], %{max_length: 100, omission: "…"})
+      response =
+        conn
+        |> get("/api/pleroma/admin/statuses")
+        |> json_response(200)
+
+      refute "private" in Enum.map(response, & &1["visibility"])
+      assert length(response) == 4
     end
 
-    test "transfer settings to DB and to file", %{conn: conn} do
-      assert Pleroma.Repo.all(Pleroma.Web.AdminAPI.Config) == []
-      ret_conn = get(conn, "/api/pleroma/admin/config/migrate_to_db")
-      assert json_response(ret_conn, 200) == %{}
-      assert Pleroma.Repo.all(Pleroma.Web.AdminAPI.Config) > 0
+    test "returns only local statuses with local_only on", %{conn: conn} do
+      user = insert(:user)
+      remote_user = insert(:user, local: false, nickname: "archaeme@archae.me")
+      insert(:note_activity, user: user, local: true)
+      insert(:note_activity, user: remote_user, local: false)
 
-      ret_conn = get(conn, "/api/pleroma/admin/config/migrate_from_db")
+      response =
+        conn
+        |> get("/api/pleroma/admin/statuses?local_only=true")
+        |> json_response(200)
 
-      assert json_response(ret_conn, 200) == %{}
-      assert Pleroma.Repo.all(Pleroma.Web.AdminAPI.Config) == []
+      assert length(response) == 1
+    end
+
+    test "returns private statuses with godmode on", %{conn: conn} do
+      user = insert(:user)
+      {:ok, _} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
+      {:ok, _} = CommonAPI.post(user, %{"status" => ".", "visibility" => "public"})
+      conn = get(conn, "/api/pleroma/admin/statuses?godmode=true")
+      assert json_response(conn, 200) |> length() == 2
     end
   end
 
@@ -2564,6 +3160,20 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
       assert json_response(conn, 200) |> length() == 5
     end
+
+    test "excludes reblogs by default", %{conn: conn, user: user} do
+      other_user = insert(:user)
+      {:ok, activity} = CommonAPI.post(user, %{"status" => "."})
+      {:ok, %Activity{}, _} = CommonAPI.repeat(activity.id, other_user)
+
+      conn_res = get(conn, "/api/pleroma/admin/users/#{other_user.nickname}/statuses")
+      assert json_response(conn_res, 200) |> length() == 0
+
+      conn_res =
+        get(conn, "/api/pleroma/admin/users/#{other_user.nickname}/statuses?with_reblogs=true")
+
+      assert json_response(conn_res, 200) |> length() == 1
+    end
   end
 
   describe "GET /api/pleroma/admin/moderation_log" do
@@ -2846,7 +3456,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
       user = insert(:user, local: false, nickname: "archaeme@archae.me")
       user2 = insert(:user, local: false, nickname: "test@test.com")
       insert_pair(:note_activity, user: user)
-      insert(:note_activity, user: user2)
+      activity = insert(:note_activity, user: user2)
 
       ret_conn = get(conn, "/api/pleroma/admin/instances/archae.me/statuses")
 
@@ -2865,6 +3475,16 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
       response = json_response(ret_conn, 200)
 
       assert Enum.empty?(response)
+
+      CommonAPI.repeat(activity.id, user)
+
+      ret_conn = get(conn, "/api/pleroma/admin/instances/archae.me/statuses")
+      response = json_response(ret_conn, 200)
+      assert length(response) == 2
+
+      ret_conn = get(conn, "/api/pleroma/admin/instances/archae.me/statuses?with_reblogs=true")
+      response = json_response(ret_conn, 200)
+      assert length(response) == 3
     end
   end
 
@@ -2979,6 +3599,40 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
       assert ReportNote |> Repo.all() |> length() == 1
     end
   end
+
+  test "GET /api/pleroma/admin/config/descriptions", %{conn: conn} do
+    admin = insert(:user, is_admin: true)
+
+    conn =
+      assign(conn, :user, admin)
+      |> get("/api/pleroma/admin/config/descriptions")
+
+    assert [child | _others] = json_response(conn, 200)
+
+    assert child["children"]
+    assert child["key"]
+    assert String.starts_with?(child["group"], ":")
+    assert child["description"]
+  end
+
+  describe "/api/pleroma/admin/stats" do
+    test "status visibility count", %{conn: conn} do
+      admin = insert(:user, is_admin: true)
+      user = insert(:user)
+      CommonAPI.post(user, %{"visibility" => "public", "status" => "hey"})
+      CommonAPI.post(user, %{"visibility" => "unlisted", "status" => "hey"})
+      CommonAPI.post(user, %{"visibility" => "unlisted", "status" => "hey"})
+
+      response =
+        conn
+        |> assign(:user, admin)
+        |> get("/api/pleroma/admin/stats")
+        |> json_response(200)
+
+      assert %{"direct" => 0, "private" => 0, "public" => 1, "unlisted" => 2} =
+               response["status_visibility"]
+    end
+  end
 end
 
 # Needed for testing