Merge branch 'fix/following-request-from-deactivated' into 'develop'
authorlain <lain@soykaf.club>
Thu, 25 Jun 2020 06:50:44 +0000 (06:50 +0000)
committerlain <lain@soykaf.club>
Thu, 25 Jun 2020 06:50:44 +0000 (06:50 +0000)
Filter outstanding follower requests from deactivated accounts

Closes #1771

See merge request pleroma/pleroma!2682

18 files changed:
CHANGELOG.md
config/config.exs
docs/API/admin_api.md
lib/mix/tasks/pleroma/refresh_counter_cache.ex
lib/pleroma/counter_cache.ex
lib/pleroma/emoji/pack.ex
lib/pleroma/http/http.ex
lib/pleroma/http/tzdata.ex [new file with mode: 0644]
lib/pleroma/stats.ex
lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
mix.exs
mix.lock
priv/repo/migrations/20200508092434_update_counter_cache_table.exs [new file with mode: 0644]
test/http/tzdata_test.exs [new file with mode: 0644]
test/http_test.exs
test/stats_test.exs
test/tasks/refresh_counter_cache_test.exs
test/web/admin_api/controllers/admin_api_controller_test.exs

index 7fc2231d147bb36f9a3d3df36fcbb996600f1cfa..71963d206e7a3bac5ef1e0cbd03c0fd487932383 100644 (file)
@@ -15,9 +15,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 <details>
   <summary>API Changes</summary>
+
 - **Breaking:** Emoji API: changed methods and renamed routes.
 </details>
 
+<details>
+  <summary>Admin API Changes</summary>
+
+- Status visibility stats: now can return stats per instance.
+
+- Mix task to refresh counter cache (`mix pleroma.refresh_counter_cache`)
+</details>
+
 ### Removed
 - **Breaking:** removed `with_move` parameter from notifications timeline.
 
@@ -98,6 +107,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 2. Run database migrations (inside Pleroma directory):
   - OTP: `./bin/pleroma_ctl migrate`
   - From Source: `mix ecto.migrate`
+3. Reset status visibility counters (inside Pleroma directory):
+  - OTP: `./bin/pleroma_ctl refresh_counter_cache`
+  - From Source: `mix pleroma.refresh_counter_cache`
 
 
 ## [2.0.2] - 2020-04-08
index a81ffcd3b9ed77e65613824585d3b1d0b64a7c35..bd559c835578ff9f77d9c765824f88247cebb4f2 100644 (file)
@@ -695,6 +695,8 @@ config :pleroma, :mrf,
   transparency: true,
   transparency_exclusions: []
 
+config :tzdata, :http_client, Pleroma.HTTP.Tzdata
+
 # Import environment specific config. This must remain at the bottom
 # of this file so it overrides the configuration defined above.
 import_config "#{Mix.env()}.exs"
index b6fb43dcbb9633fca79f9c086880791fd8c823ef..baf895d90923fe7ed33f55e299caec0256f60d99 100644 (file)
@@ -1118,6 +1118,10 @@ Loads json generated from `config/descriptions.exs`.
 
 ### Stats
 
+- Query Params:
+  - *optional* `instance`: **string** instance hostname (without protocol) to get stats for
+- Example: `https://mypleroma.org/api/pleroma/admin/stats?instance=lain.com`
+
 - Response:
 
 ```json
index 15b4dbfa681c9e86bbde1d494121ebff0dc2d3bf..efcbaa3b1e9fc9962ae2a2a770059db4358ee4bd 100644 (file)
@@ -17,30 +17,53 @@ defmodule Mix.Tasks.Pleroma.RefreshCounterCache do
   def run([]) do
     Mix.Pleroma.start_pleroma()
 
-    ["public", "unlisted", "private", "direct"]
-    |> Enum.each(fn visibility ->
-      count = status_visibility_count_query(visibility)
-      name = "status_visibility_#{visibility}"
-      CounterCache.set(name, count)
-      Mix.Pleroma.shell_info("Set #{name} to #{count}")
+    instances =
+      Activity
+      |> distinct([a], true)
+      |> select([a], fragment("split_part(?, '/', 3)", a.actor))
+      |> Repo.all()
+
+    instances
+    |> Enum.with_index(1)
+    |> Enum.each(fn {instance, i} ->
+      counters = instance_counters(instance)
+      CounterCache.set(instance, counters)
+
+      Mix.Pleroma.shell_info(
+        "[#{i}/#{length(instances)}] Setting #{instance} counters: #{inspect(counters)}"
+      )
     end)
 
     Mix.Pleroma.shell_info("Done")
   end
 
-  defp status_visibility_count_query(visibility) do
+  defp instance_counters(instance) do
+    counters = %{"public" => 0, "unlisted" => 0, "private" => 0, "direct" => 0}
+
     Activity
-    |> where(
+    |> where([a], fragment("(? ->> 'type'::text) = 'Create'", a.data))
+    |> where([a], fragment("split_part(?, '/', 3) = ?", a.actor, ^instance))
+    |> select(
+      [a],
+      {fragment(
+         "activity_visibility(?, ?, ?)",
+         a.actor,
+         a.recipients,
+         a.data
+       ), count(a.id)}
+    )
+    |> group_by(
       [a],
       fragment(
-        "activity_visibility(?, ?, ?) = ?",
+        "activity_visibility(?, ?, ?)",
         a.actor,
         a.recipients,
-        a.data,
-        ^visibility
+        a.data
       )
     )
-    |> where([a], fragment("(? ->> 'type'::text) = 'Create'", a.data))
-    |> Repo.aggregate(:count, :id, timeout: :timer.minutes(30))
+    |> Repo.all(timeout: :timer.minutes(30))
+    |> Enum.reduce(counters, fn {visibility, count}, acc ->
+      Map.put(acc, visibility, count)
+    end)
   end
 end
index 4d348a4134915a783d08547ed8fffae233721d5c..ebd1f603df4c5f7d650a1f5576162f791fb1535a 100644 (file)
@@ -10,32 +10,70 @@ defmodule Pleroma.CounterCache do
   import Ecto.Query
 
   schema "counter_cache" do
-    field(:name, :string)
-    field(:count, :integer)
+    field(:instance, :string)
+    field(:public, :integer)
+    field(:unlisted, :integer)
+    field(:private, :integer)
+    field(:direct, :integer)
   end
 
   def changeset(struct, params) do
     struct
-    |> cast(params, [:name, :count])
-    |> validate_required([:name])
-    |> unique_constraint(:name)
+    |> cast(params, [:instance, :public, :unlisted, :private, :direct])
+    |> validate_required([:instance])
+    |> unique_constraint(:instance)
   end
 
-  def get_as_map(names) when is_list(names) do
+  def get_by_instance(instance) do
     CounterCache
-    |> where([cc], cc.name in ^names)
-    |> Repo.all()
-    |> Enum.group_by(& &1.name, & &1.count)
-    |> Map.new(fn {k, v} -> {k, hd(v)} end)
+    |> select([c], %{
+      "public" => c.public,
+      "unlisted" => c.unlisted,
+      "private" => c.private,
+      "direct" => c.direct
+    })
+    |> where([c], c.instance == ^instance)
+    |> Repo.one()
+    |> case do
+      nil -> %{"public" => 0, "unlisted" => 0, "private" => 0, "direct" => 0}
+      val -> val
+    end
   end
 
-  def set(name, count) do
+  def get_sum do
+    CounterCache
+    |> select([c], %{
+      "public" => type(sum(c.public), :integer),
+      "unlisted" => type(sum(c.unlisted), :integer),
+      "private" => type(sum(c.private), :integer),
+      "direct" => type(sum(c.direct), :integer)
+    })
+    |> Repo.one()
+  end
+
+  def set(instance, values) do
+    params =
+      Enum.reduce(
+        ["public", "private", "unlisted", "direct"],
+        %{"instance" => instance},
+        fn param, acc ->
+          Map.put_new(acc, param, Map.get(values, param, 0))
+        end
+      )
+
     %CounterCache{}
-    |> changeset(%{"name" => name, "count" => count})
+    |> changeset(params)
     |> Repo.insert(
-      on_conflict: [set: [count: count]],
+      on_conflict: [
+        set: [
+          public: params["public"],
+          private: params["private"],
+          unlisted: params["unlisted"],
+          direct: params["direct"]
+        ]
+      ],
       returning: true,
-      conflict_target: :name
+      conflict_target: :instance
     )
   end
 end
index 787ff8141772f3738cb7c041ce789a1ff6d82ad1..d076ae3125c5318815dba7d59042862c9f1399e8 100644 (file)
@@ -45,6 +45,7 @@ defmodule Pleroma.Emoji.Pack do
       shortcodes =
         pack.files
         |> Map.keys()
+        |> Enum.sort()
         |> paginate(opts[:page], opts[:page_size])
 
       pack = Map.put(pack, :files, Map.take(pack.files, shortcodes))
index 583b564842fe79477f0205ef758ab2b5c094351f..66ca7536766918a3ffa6734fe301aa3857cfcb6e 100644 (file)
@@ -16,6 +16,7 @@ defmodule Pleroma.HTTP do
   require Logger
 
   @type t :: __MODULE__
+  @type method() :: :get | :post | :put | :delete | :head
 
   @doc """
   Performs GET request.
@@ -28,6 +29,9 @@ defmodule Pleroma.HTTP do
   def get(nil, _, _), do: nil
   def get(url, headers, options), do: request(:get, url, "", headers, options)
 
+  @spec head(Request.url(), Request.headers(), keyword()) :: {:ok, Env.t()} | {:error, any()}
+  def head(url, headers \\ [], options \\ []), do: request(:head, url, "", headers, options)
+
   @doc """
   Performs POST request.
 
@@ -42,7 +46,7 @@ defmodule Pleroma.HTTP do
   Builds and performs http request.
 
   # Arguments:
-  `method` - :get, :post, :put, :delete
+  `method` - :get, :post, :put, :delete, :head
   `url` - full url
   `body` - request body
   `headers` - a keyworld list of headers, e.g. `[{"content-type", "text/plain"}]`
@@ -52,7 +56,7 @@ defmodule Pleroma.HTTP do
   `{:ok, %Tesla.Env{}}` or `{:error, error}`
 
   """
-  @spec request(atom(), Request.url(), String.t(), Request.headers(), keyword()) ::
+  @spec request(method(), Request.url(), String.t(), Request.headers(), keyword()) ::
           {:ok, Env.t()} | {:error, any()}
   def request(method, url, body, headers, options) when is_binary(url) do
     uri = URI.parse(url)
diff --git a/lib/pleroma/http/tzdata.ex b/lib/pleroma/http/tzdata.ex
new file mode 100644 (file)
index 0000000..34bb253
--- /dev/null
@@ -0,0 +1,25 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.HTTP.Tzdata do
+  @moduledoc false
+
+  @behaviour Tzdata.HTTPClient
+
+  alias Pleroma.HTTP
+
+  @impl true
+  def get(url, headers, options) do
+    with {:ok, %Tesla.Env{} = env} <- HTTP.get(url, headers, options) do
+      {:ok, {env.status, env.headers, env.body}}
+    end
+  end
+
+  @impl true
+  def head(url, headers, options) do
+    with {:ok, %Tesla.Env{} = env} <- HTTP.head(url, headers, options) do
+      {:ok, {env.status, env.headers}}
+    end
+  end
+end
index 6b3a8a41f738801e8ffc0817d066cd3212d0ef74..9a03f01db7087d1766599908748a062050b14354 100644 (file)
@@ -97,20 +97,11 @@ defmodule Pleroma.Stats do
     }
   end
 
-  def get_status_visibility_count do
-    counter_cache =
-      CounterCache.get_as_map([
-        "status_visibility_public",
-        "status_visibility_private",
-        "status_visibility_unlisted",
-        "status_visibility_direct"
-      ])
-
-    %{
-      public: counter_cache["status_visibility_public"] || 0,
-      unlisted: counter_cache["status_visibility_unlisted"] || 0,
-      private: counter_cache["status_visibility_private"] || 0,
-      direct: counter_cache["status_visibility_direct"] || 0
-    }
+  def get_status_visibility_count(instance \\ nil) do
+    if is_nil(instance) do
+      CounterCache.get_sum()
+    else
+      CounterCache.get_by_instance(instance)
+    end
   end
 end
index db2413dfe6015c6b916bc2bb862e2d8e035d2874..f9545d8950caf135aca2ec223ada5113a4ded57b 100644 (file)
@@ -643,10 +643,10 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
     json(conn, "")
   end
 
-  def stats(conn, _) do
-    count = Stats.get_status_visibility_count()
+  def stats(conn, params) do
+    counters = Stats.get_status_visibility_count(params["instance"])
 
-    json(conn, %{"status_visibility" => count})
+    json(conn, %{"status_visibility" => counters})
   end
 
   defp page_params(params) do
diff --git a/mix.exs b/mix.exs
index 4d13e95d7195f3f7548f670d5ca4b0396b2ba946..b638be541870d1ca44a6a822179b7c2556b9f03b 100644 (file)
--- a/mix.exs
+++ b/mix.exs
@@ -117,7 +117,7 @@ defmodule Pleroma.Mixfile do
   defp deps do
     [
       {:phoenix, "~> 1.4.8"},
-      {:tzdata, "~> 0.5.21"},
+      {:tzdata, "~> 1.0.3"},
       {:plug_cowboy, "~> 2.0"},
       {:phoenix_pubsub, "~> 1.1"},
       {:phoenix_ecto, "~> 4.0"},
index 5383c2c6ed5ae3b7d5645a83e8aeb949700b93c9..5ad49391dbab3a0ab009aad04fe9f8482b27a97c 100644 (file)
--- a/mix.lock
+++ b/mix.lock
   "tesla": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/tesla.git", "61b7503cef33f00834f78ddfafe0d5d9dec2270b", [ref: "61b7503cef33f00834f78ddfafe0d5d9dec2270b"]},
   "timex": {:hex, :timex, "3.6.1", "efdf56d0e67a6b956cc57774353b0329c8ab7726766a11547e529357ffdc1d56", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "f354efb2400dd7a80fd9eb6c8419068c4f632da4ac47f3d8822d6e33f08bc852"},
   "trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},
-  "tzdata": {:hex, :tzdata, "0.5.22", "f2ba9105117ee0360eae2eca389783ef7db36d533899b2e84559404dbc77ebb8", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "cd66c8a1e6a9e121d1f538b01bef459334bb4029a1ffb4eeeb5e4eae0337e7b6"},
+  "tzdata": {:hex, :tzdata, "1.0.3", "73470ad29dde46e350c60a66e6b360d3b99d2d18b74c4c349dbebbc27a09a3eb", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a6e1ee7003c4d04ecbd21dd3ec690d4c6662db5d3bbdd7262d53cdf5e7c746c1"},
   "ueberauth": {:hex, :ueberauth, "0.6.2", "25a31111249d60bad8b65438b2306a4dc91f3208faa62f5a8c33e8713989b2e8", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "db9fbfb5ac707bc4f85a297758406340bf0358b4af737a88113c1a9eee120ac7"},
   "unicode_util_compat": {:hex, :unicode_util_compat, "0.5.0", "8516502659002cec19e244ebd90d312183064be95025a319a6c7e89f4bccd65b", [:rebar3], [], "hexpm", "d48d002e15f5cc105a696cf2f1bbb3fc72b4b770a184d8420c8db20da2674b38"},
   "unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm", "6c7729a2d214806450d29766abc2afaa7a2cbecf415be64f36a6691afebb50e5"},
diff --git a/priv/repo/migrations/20200508092434_update_counter_cache_table.exs b/priv/repo/migrations/20200508092434_update_counter_cache_table.exs
new file mode 100644 (file)
index 0000000..7383448
--- /dev/null
@@ -0,0 +1,143 @@
+defmodule Pleroma.Repo.Migrations.UpdateCounterCacheTable do
+  use Ecto.Migration
+
+  @function_name "update_status_visibility_counter_cache"
+  @trigger_name "status_visibility_counter_cache_trigger"
+
+  def up do
+    execute("drop trigger if exists #{@trigger_name} on activities")
+    execute("drop function if exists #{@function_name}()")
+    drop_if_exists(unique_index(:counter_cache, [:name]))
+    drop_if_exists(table(:counter_cache))
+
+    create_if_not_exists table(:counter_cache) do
+      add(:instance, :string, null: false)
+      add(:direct, :bigint, null: false, default: 0)
+      add(:private, :bigint, null: false, default: 0)
+      add(:unlisted, :bigint, null: false, default: 0)
+      add(:public, :bigint, null: false, default: 0)
+    end
+
+    create_if_not_exists(unique_index(:counter_cache, [:instance]))
+
+    """
+    CREATE OR REPLACE FUNCTION #{@function_name}()
+    RETURNS TRIGGER AS
+    $$
+      DECLARE
+        hostname character varying(255);
+        visibility_new character varying(64);
+        visibility_old character varying(64);
+        actor character varying(255);
+      BEGIN
+      IF TG_OP = 'DELETE' THEN
+        actor := OLD.actor;
+      ELSE
+        actor := NEW.actor;
+      END IF;
+      hostname := split_part(actor, '/', 3);
+      IF TG_OP = 'INSERT' THEN
+        visibility_new := activity_visibility(NEW.actor, NEW.recipients, NEW.data);
+        IF NEW.data->>'type' = 'Create'
+            AND visibility_new IN ('public', 'unlisted', 'private', 'direct') THEN
+          EXECUTE format('INSERT INTO "counter_cache" ("instance", %1$I) VALUES ($1, 1)
+                          ON CONFLICT ("instance") DO
+                          UPDATE SET %1$I = "counter_cache".%1$I + 1', visibility_new)
+                          USING hostname;
+        END IF;
+        RETURN NEW;
+      ELSIF TG_OP = 'UPDATE' THEN
+        visibility_new := activity_visibility(NEW.actor, NEW.recipients, NEW.data);
+        visibility_old := activity_visibility(OLD.actor, OLD.recipients, OLD.data);
+        IF (NEW.data->>'type' = 'Create')
+            AND (OLD.data->>'type' = 'Create')
+            AND visibility_new != visibility_old
+            AND visibility_new IN ('public', 'unlisted', 'private', 'direct') THEN
+          EXECUTE format('UPDATE "counter_cache" SET
+                          %1$I = greatest("counter_cache".%1$I - 1, 0),
+                          %2$I = "counter_cache".%2$I + 1
+                          WHERE "instance" = $1', visibility_old, visibility_new)
+                          USING hostname;
+        END IF;
+        RETURN NEW;
+      ELSIF TG_OP = 'DELETE' THEN
+        IF OLD.data->>'type' = 'Create' THEN
+          visibility_old := activity_visibility(OLD.actor, OLD.recipients, OLD.data);
+          EXECUTE format('UPDATE "counter_cache" SET
+                          %1$I = greatest("counter_cache".%1$I - 1, 0)
+                          WHERE "instance" = $1', visibility_old)
+                          USING hostname;
+        END IF;
+        RETURN OLD;
+      END IF;
+      END;
+    $$
+    LANGUAGE 'plpgsql';
+    """
+    |> execute()
+
+    execute("DROP TRIGGER IF EXISTS #{@trigger_name} ON activities")
+
+    """
+    CREATE TRIGGER #{@trigger_name}
+    BEFORE
+      INSERT
+      OR UPDATE of recipients, data
+      OR DELETE
+    ON activities
+    FOR EACH ROW
+      EXECUTE PROCEDURE #{@function_name}();
+    """
+    |> execute()
+  end
+
+  def down do
+    execute("DROP TRIGGER IF EXISTS #{@trigger_name} ON activities")
+    execute("DROP FUNCTION IF EXISTS #{@function_name}()")
+    drop_if_exists(unique_index(:counter_cache, [:instance]))
+    drop_if_exists(table(:counter_cache))
+
+    create_if_not_exists table(:counter_cache) do
+      add(:name, :string, null: false)
+      add(:count, :bigint, null: false, default: 0)
+    end
+
+    create_if_not_exists(unique_index(:counter_cache, [:name]))
+
+    """
+    CREATE OR REPLACE FUNCTION #{@function_name}()
+    RETURNS TRIGGER AS
+    $$
+      DECLARE
+      BEGIN
+      IF TG_OP = 'INSERT' THEN
+          IF NEW.data->>'type' = 'Create' THEN
+            EXECUTE 'INSERT INTO counter_cache (name, count) VALUES (''status_visibility_' || activity_visibility(NEW.actor, NEW.recipients, NEW.data) || ''', 1) ON CONFLICT (name) DO UPDATE SET count = counter_cache.count + 1';
+          END IF;
+          RETURN NEW;
+      ELSIF TG_OP = 'UPDATE' THEN
+          IF (NEW.data->>'type' = 'Create') and (OLD.data->>'type' = 'Create') and activity_visibility(NEW.actor, NEW.recipients, NEW.data) != activity_visibility(OLD.actor, OLD.recipients, OLD.data) THEN
+             EXECUTE 'INSERT INTO counter_cache (name, count) VALUES (''status_visibility_' || activity_visibility(NEW.actor, NEW.recipients, NEW.data) || ''', 1) ON CONFLICT (name) DO UPDATE SET count = counter_cache.count + 1';
+             EXECUTE 'update counter_cache SET count = counter_cache.count - 1 where count > 0 and name = ''status_visibility_' || activity_visibility(OLD.actor, OLD.recipients, OLD.data) || ''';';
+          END IF;
+          RETURN NEW;
+      ELSIF TG_OP = 'DELETE' THEN
+          IF OLD.data->>'type' = 'Create' THEN
+            EXECUTE 'update counter_cache SET count = counter_cache.count - 1 where count > 0 and name = ''status_visibility_' || activity_visibility(OLD.actor, OLD.recipients, OLD.data) || ''';';
+          END IF;
+          RETURN OLD;
+      END IF;
+      END;
+    $$
+    LANGUAGE 'plpgsql';
+    """
+    |> execute()
+
+    """
+    CREATE TRIGGER #{@trigger_name} BEFORE INSERT OR UPDATE of recipients, data OR DELETE ON activities
+    FOR EACH ROW
+    EXECUTE PROCEDURE #{@function_name}();
+    """
+    |> execute()
+  end
+end
diff --git a/test/http/tzdata_test.exs b/test/http/tzdata_test.exs
new file mode 100644 (file)
index 0000000..3e605d3
--- /dev/null
@@ -0,0 +1,35 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.HTTP.TzdataTest do
+  use ExUnit.Case
+
+  import Tesla.Mock
+  alias Pleroma.HTTP
+  @url "https://data.iana.org/time-zones/tzdata-latest.tar.gz"
+
+  setup do
+    mock(fn
+      %{method: :head, url: @url} ->
+        %Tesla.Env{status: 200, body: ""}
+
+      %{method: :get, url: @url} ->
+        %Tesla.Env{status: 200, body: "hello"}
+    end)
+
+    :ok
+  end
+
+  describe "head/1" do
+    test "returns successfully result" do
+      assert HTTP.Tzdata.head(@url, [], []) == {:ok, {200, []}}
+    end
+  end
+
+  describe "get/1" do
+    test "returns successfully result" do
+      assert HTTP.Tzdata.get(@url, [], []) == {:ok, {200, [], "hello"}}
+    end
+  end
+end
index 618485b552c4547e4d00c2831ad42fe67a588d6d..d394bb94222770ef9217023e769a5e4888a1353c 100644 (file)
@@ -17,6 +17,9 @@ defmodule Pleroma.HTTPTest do
       } ->
         json(%{"my" => "data"})
 
+      %{method: :head, url: "http://example.com/hello"} ->
+        %Tesla.Env{status: 200, body: ""}
+
       %{method: :get, url: "http://example.com/hello"} ->
         %Tesla.Env{status: 200, body: "hello"}
 
@@ -27,6 +30,12 @@ defmodule Pleroma.HTTPTest do
     :ok
   end
 
+  describe "head/1" do
+    test "returns successfully result" do
+      assert HTTP.head("http://example.com/hello") == {:ok, %Tesla.Env{status: 200, body: ""}}
+    end
+  end
+
   describe "get/1" do
     test "returns successfully result" do
       assert HTTP.get("http://example.com/hello") == {
index 4b76e2e782b12de65e9d9f94e343b58e28fc7239..f09d8d31a689f69fc52a211cc538695fbc599ede 100644 (file)
@@ -17,10 +17,11 @@ defmodule Pleroma.StatsTest do
     end
   end
 
-  describe "status visibility count" do
+  describe "status visibility sum count" do
     test "on new status" do
+      instance2 = "instance2.tld"
       user = insert(:user)
-      other_user = insert(:user)
+      other_user = insert(:user, %{ap_id: "https://#{instance2}/@actor"})
 
       CommonAPI.post(user, %{visibility: "public", status: "hey"})
 
@@ -45,24 +46,24 @@ defmodule Pleroma.StatsTest do
         })
       end)
 
-      assert %{direct: 3, private: 4, public: 1, unlisted: 2} =
+      assert %{"direct" => 3, "private" => 4, "public" => 1, "unlisted" => 2} =
                Pleroma.Stats.get_status_visibility_count()
     end
 
     test "on status delete" do
       user = insert(:user)
       {:ok, activity} = CommonAPI.post(user, %{visibility: "public", status: "hey"})
-      assert %{public: 1} = Pleroma.Stats.get_status_visibility_count()
+      assert %{"public" => 1} = Pleroma.Stats.get_status_visibility_count()
       CommonAPI.delete(activity.id, user)
-      assert %{public: 0} = Pleroma.Stats.get_status_visibility_count()
+      assert %{"public" => 0} = Pleroma.Stats.get_status_visibility_count()
     end
 
     test "on status visibility update" do
       user = insert(:user)
       {:ok, activity} = CommonAPI.post(user, %{visibility: "public", status: "hey"})
-      assert %{public: 1, private: 0} = Pleroma.Stats.get_status_visibility_count()
+      assert %{"public" => 1, "private" => 0} = Pleroma.Stats.get_status_visibility_count()
       {:ok, _} = CommonAPI.update_activity_scope(activity.id, %{visibility: "private"})
-      assert %{public: 0, private: 1} = Pleroma.Stats.get_status_visibility_count()
+      assert %{"public" => 0, "private" => 1} = Pleroma.Stats.get_status_visibility_count()
     end
 
     test "doesn't count unrelated activities" do
@@ -73,8 +74,46 @@ defmodule Pleroma.StatsTest do
       CommonAPI.favorite(other_user, activity.id)
       CommonAPI.repeat(activity.id, other_user)
 
-      assert %{direct: 0, private: 0, public: 1, unlisted: 0} =
+      assert %{"direct" => 0, "private" => 0, "public" => 1, "unlisted" => 0} =
                Pleroma.Stats.get_status_visibility_count()
     end
   end
+
+  describe "status visibility by instance count" do
+    test "single instance" do
+      local_instance = Pleroma.Web.Endpoint.url() |> String.split("//") |> Enum.at(1)
+      instance2 = "instance2.tld"
+      user1 = insert(:user)
+      user2 = insert(:user, %{ap_id: "https://#{instance2}/@actor"})
+
+      CommonAPI.post(user1, %{visibility: "public", status: "hey"})
+
+      Enum.each(1..5, fn _ ->
+        CommonAPI.post(user1, %{
+          visibility: "unlisted",
+          status: "hey"
+        })
+      end)
+
+      Enum.each(1..10, fn _ ->
+        CommonAPI.post(user1, %{
+          visibility: "direct",
+          status: "hey @#{user2.nickname}"
+        })
+      end)
+
+      Enum.each(1..20, fn _ ->
+        CommonAPI.post(user2, %{
+          visibility: "private",
+          status: "hey"
+        })
+      end)
+
+      assert %{"direct" => 10, "private" => 0, "public" => 1, "unlisted" => 5} =
+               Pleroma.Stats.get_status_visibility_count(local_instance)
+
+      assert %{"direct" => 0, "private" => 20, "public" => 0, "unlisted" => 0} =
+               Pleroma.Stats.get_status_visibility_count(instance2)
+    end
+  end
 end
index 851971a778ef42cbe1c6e5f24b2094a70d11668d..6a1a9ac1741a3d804afc69efd535ebfa071f00d7 100644 (file)
@@ -37,7 +37,7 @@ defmodule Mix.Tasks.Pleroma.RefreshCounterCacheTest do
 
     assert capture_io(fn -> Mix.Tasks.Pleroma.RefreshCounterCache.run([]) end) =~ "Done\n"
 
-    assert %{direct: 3, private: 4, public: 1, unlisted: 2} =
+    assert %{"direct" => 3, "private" => 4, "public" => 1, "unlisted" => 2} =
              Pleroma.Stats.get_status_visibility_count()
   end
 end
index 3a3eb822da48af1e593d6b596941e875e8283343..48fb108ec83ee5c14d6b9a4c3490f589b1884679 100644 (file)
@@ -1732,6 +1732,26 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
       assert %{"direct" => 0, "private" => 0, "public" => 1, "unlisted" => 2} =
                response["status_visibility"]
     end
+
+    test "by instance", %{conn: conn} do
+      admin = insert(:user, is_admin: true)
+      user1 = insert(:user)
+      instance2 = "instance2.tld"
+      user2 = insert(:user, %{ap_id: "https://#{instance2}/@actor"})
+
+      CommonAPI.post(user1, %{visibility: "public", status: "hey"})
+      CommonAPI.post(user2, %{visibility: "unlisted", status: "hey"})
+      CommonAPI.post(user2, %{visibility: "private", status: "hey"})
+
+      response =
+        conn
+        |> assign(:user, admin)
+        |> get("/api/pleroma/admin/stats", instance: instance2)
+        |> json_response(200)
+
+      assert %{"direct" => 0, "private" => 1, "public" => 0, "unlisted" => 1} =
+               response["status_visibility"]
+    end
   end
 end