Browse Source

Merge remote-tracking branch 'remotes/upstream/develop' into 1335-user-api-id-fields-relations

# Conflicts:
#	mix.lock
feature/compile_get
Ivan Tashkinov 4 years ago
parent
commit
52cc7de82c
18 changed files with 397 additions and 110 deletions
  1. +14
    -0
      .gitlab-ci.yml
  2. +1
    -0
      config/prod.exs
  3. +13
    -0
      lib/pleroma/activity.ex
  4. +0
    -25
      lib/pleroma/user.ex
  5. +5
    -6
      lib/pleroma/web/mastodon_api/views/account_view.ex
  6. +2
    -8
      lib/pleroma/web/mastodon_api/views/status_view.ex
  7. +4
    -1
      lib/pleroma/web/push/impl.ex
  8. +1
    -1
      mix.exs
  9. +4
    -4
      mix.lock
  10. +53
    -0
      priv/repo/migrations/20191128153944_fix_missing_following_count.exs
  11. +47
    -0
      test/federation/federation_test.exs
  12. +218
    -0
      test/support/cluster.ex
  13. +2
    -1
      test/test_helper.exs
  14. +16
    -62
      test/user_test.exs
  15. +5
    -0
      test/web/activity_pub/transmogrifier_test.exs
  16. +1
    -0
      test/web/admin_api/admin_api_controller_test.exs
  17. +10
    -1
      test/web/mastodon_api/views/account_view_test.exs
  18. +1
    -1
      test/web/streamer/streamer_test.exs

+ 14
- 0
.gitlab-ci.yml View File

@@ -31,6 +31,7 @@ build:

benchmark:
stage: benchmark
when: manual
variables:
MIX_ENV: benchmark
services:
@@ -55,6 +56,19 @@ unit-testing:
- mix ecto.migrate
- mix coveralls --preload-modules

federated-testing:
stage: test
services:
- name: minibikini/postgres-with-rum:12
alias: postgres
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
script:
- mix deps.get
- mix ecto.create
- mix ecto.migrate
- epmd -daemon
- mix test --trace --only federated

unit-testing-rum:
stage: test
services:


+ 1
- 0
config/prod.exs View File

@@ -21,6 +21,7 @@ config :phoenix, serve_endpoints: true

# Do not print debug messages in production
config :logger, :console, level: :warn
config :logger, :ex_syslogger, level: :warn

# ## SSL Support
#


+ 13
- 0
lib/pleroma/activity.ex View File

@@ -303,4 +303,17 @@ defmodule Pleroma.Activity do
end

defdelegate search(user, query, options \\ []), to: Pleroma.Activity.Search

def direct_conversation_id(activity, for_user) do
alias Pleroma.Conversation.Participation

with %{data: %{"context" => context}} when is_binary(context) <- activity,
%Pleroma.Conversation{} = conversation <- Pleroma.Conversation.get_for_ap_id(context),
%Participation{id: participation_id} <-
Participation.for_user_and_conversation(for_user, conversation) do
participation_id
else
_ -> nil
end
end
end

+ 0
- 25
lib/pleroma/user.ex View File

@@ -272,20 +272,6 @@ defmodule Pleroma.User do
def ap_following(%User{following_address: fa}) when is_binary(fa), do: fa
def ap_following(%User{} = user), do: "#{ap_id(user)}/following"

def user_info(%User{} = user, args \\ %{}) do
following_count = Map.get(args, :following_count, user.following_count)
follower_count = Map.get(args, :follower_count, user.follower_count)

%{
note_count: user.note_count,
locked: user.locked,
confirmation_pending: user.confirmation_pending,
default_scope: user.default_scope,
follower_count: follower_count,
following_count: following_count
}
end

def follow_state(%User{} = user, %User{} = target) do
case Utils.fetch_latest_follow(user, target) do
%{data: %{"state" => state}} -> state
@@ -304,10 +290,6 @@ defmodule Pleroma.User do
Cachex.put(:user_cache, "follow_state:#{user_ap_id}|#{target_ap_id}", state)
end

def set_info_cache(user, args) do
Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user, args))
end

@spec restrict_deactivated(Ecto.Query.t()) :: Ecto.Query.t()
def restrict_deactivated(query) do
from(u in query, where: u.deactivated != ^true)
@@ -709,7 +691,6 @@ defmodule Pleroma.User do
def set_cache(%User{} = user) do
Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user))
{:ok, user}
end

@@ -728,7 +709,6 @@ defmodule Pleroma.User do
def invalidate_cache(user) do
Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
Cachex.del(:user_cache, "nickname:#{user.nickname}")
Cachex.del(:user_cache, "user_info:#{user.id}")
end

def get_cached_by_ap_id(ap_id) do
@@ -796,11 +776,6 @@ defmodule Pleroma.User do
get_by_nickname(nickname_or_email) || get_by_email(nickname_or_email)
end

def get_cached_user_info(user) do
key = "user_info:#{user.id}"
Cachex.fetch!(:user_cache, key, fn -> user_info(user) end)
end

def fetch_by_nickname(nickname), do: ActivityPub.make_user_from_nickname(nickname)

def get_or_fetch_by_nickname(nickname) do


+ 5
- 6
lib/pleroma/web/mastodon_api/views/account_view.ex View File

@@ -71,18 +71,17 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do

image = User.avatar_url(user) |> MediaProxy.url()
header = User.banner_url(user) |> MediaProxy.url()
user_info = User.get_cached_user_info(user)

following_count =
if !user.hide_follows_count or !user.hide_follows or opts[:for] == user do
user_info.following_count
user.following_count || 0
else
0
end

followers_count =
if !user.hide_followers_count or !user.hide_followers or opts[:for] == user do
user_info.follower_count
user.follower_count || 0
else
0
end
@@ -144,7 +143,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do

# Pleroma extension
pleroma: %{
confirmation_pending: user_info.confirmation_pending,
confirmation_pending: user.confirmation_pending,
tags: user.tags,
hide_followers_count: user.hide_followers_count,
hide_follows_count: user.hide_follows_count,
@@ -157,7 +156,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
}
}
|> maybe_put_role(user, opts[:for])
|> maybe_put_settings(user, opts[:for], user_info)
|> maybe_put_settings(user, opts[:for], opts)
|> maybe_put_notification_settings(user, opts[:for])
|> maybe_put_settings_store(user, opts[:for], opts)
|> maybe_put_chat_token(user, opts[:for], opts)
@@ -191,7 +190,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
data,
%User{id: user_id} = user,
%User{id: user_id},
_user_info
_opts
) do
data
|> Kernel.put_in([:source, :privacy], user.default_scope)


+ 2
- 8
lib/pleroma/web/mastodon_api/views/status_view.ex View File

@@ -9,8 +9,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do

alias Pleroma.Activity
alias Pleroma.ActivityExpiration
alias Pleroma.Conversation
alias Pleroma.Conversation.Participation
alias Pleroma.HTML
alias Pleroma.Object
alias Pleroma.Repo
@@ -245,12 +243,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
direct_conversation_id =
with {_, nil} <- {:direct_conversation_id, opts[:direct_conversation_id]},
{_, true} <- {:include_id, opts[:with_direct_conversation_id]},
{_, %User{} = for_user} <- {:for_user, opts[:for]},
%{data: %{"context" => context}} when is_binary(context) <- activity,
%Conversation{} = conversation <- Conversation.get_for_ap_id(context),
%Participation{id: participation_id} <-
Participation.for_user_and_conversation(for_user, conversation) do
participation_id
{_, %User{} = for_user} <- {:for_user, opts[:for]} do
Activity.direct_conversation_id(activity, for_user)
else
{:direct_conversation_id, participation_id} when is_integer(participation_id) ->
participation_id


+ 4
- 1
lib/pleroma/web/push/impl.ex View File

@@ -33,6 +33,8 @@ defmodule Pleroma.Web.Push.Impl do
gcm_api_key = Application.get_env(:web_push_encryption, :gcm_api_key)
avatar_url = User.avatar_url(actor)
object = Object.normalize(activity)
user = User.get_cached_by_id(user_id)
direct_conversation_id = Activity.direct_conversation_id(activity, user)

for subscription <- fetch_subsriptions(user_id),
get_in(subscription.data, ["alerts", type]) do
@@ -45,7 +47,8 @@ defmodule Pleroma.Web.Push.Impl do
icon: avatar_url,
preferred_locale: "en",
pleroma: %{
activity_id: activity_id
activity_id: activity_id,
direct_conversation_id: direct_conversation_id
}
}
|> Jason.encode!()


+ 1
- 1
mix.exs View File

@@ -103,7 +103,7 @@ defmodule Pleroma.Mixfile do
{:ecto_enum, "~> 1.4"},
{:ecto_sql, "~> 3.2"},
{:postgrex, ">= 0.13.5"},
{:oban, "~> 0.8.1"},
{:oban, "~> 0.12.0"},
{:quantum, "~> 2.3"},
{:gettext, "~> 0.15"},
{:comeonin, "~> 4.1.1"},


+ 4
- 4
mix.lock View File

@@ -23,9 +23,9 @@
"decimal": {:hex, :decimal, "1.8.0", "ca462e0d885f09a1c5a342dbd7c1dcf27ea63548c65a65e67334f4b61803822e", [:mix], [], "hexpm"},
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm"},
"earmark": {:hex, :earmark, "1.4.2", "3aa0bd23bc4c61cf2f1e5d752d1bb470560a6f8539974f767a38923bb20e1d7f", [:mix], [], "hexpm"},
"ecto": {:hex, :ecto, "3.2.3", "51274df79862845b388733fddcf6f107d0c8c86e27abe7131fa98f8d30761bda", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
"ecto": {:hex, :ecto, "3.2.5", "76c864b77948a479e18e69cc1d0f0f4ee7cced1148ffe6a093ff91eba644f0b5", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
"ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm"},
"ecto_sql": {:hex, :ecto_sql, "3.2.0", "751cea597e8deb616084894dd75cbabfdbe7255ff01e8c058ca13f0353a3921b", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.2.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.2.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
"ecto_sql": {:hex, :ecto_sql, "3.2.2", "d10845bc147b9f61ef485cbf0973c0a337237199bd9bd30dd9542db00aadc26b", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.2.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.2.0 or ~> 0.3.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
"esshd": {:hex, :esshd, "0.1.0", "6f93a2062adb43637edad0ea7357db2702a4b80dd9683482fe00f5134e97f4c1", [:mix], [], "hexpm"},
"eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm"},
"ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [:mix], [], "hexpm"},
@@ -68,7 +68,7 @@
"myhtmlex": {:git, "https://git.pleroma.social/pleroma/myhtmlex.git", "ad0097e2f61d4953bfef20fb6abddf23b87111e6", [ref: "ad0097e2f61d4953bfef20fb6abddf23b87111e6", submodules: true]},
"nimble_parsec": {:hex, :nimble_parsec, "0.5.1", "c90796ecee0289dbb5ad16d3ad06f957b0cd1199769641c961cfe0b97db190e0", [:mix], [], "hexpm"},
"nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]},
"oban": {:hex, :oban, "0.8.1", "4bbf62eb1829f856d69aeb5069ac7036afe07db8221a17de2a9169cc7a58a318", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
"oban": {:hex, :oban, "0.12.0", "5477d5ab4a5a201c0b6c89764040ebfc5d2c71c488a36f378016ce5990838f0f", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"},
"pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.4", "8dd29ed783f2e12195d7e0a4640effc0a7c37e6537da491f1db01839eee6d053", [:mix], [], "hexpm"},
"phoenix": {:hex, :phoenix, "1.4.10", "619e4a545505f562cd294df52294372d012823f4fd9d34a6657a8b242898c255", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
@@ -98,7 +98,7 @@
"sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm"},
"swoosh": {:hex, :swoosh, "0.23.5", "bfd9404bbf5069b1be2ffd317923ce57e58b332e25dbca2a35dedd7820dfee5a", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm"},
"syslog": {:git, "https://github.com/Vagabond/erlang-syslog.git", "4a6c6f2c996483e86c1320e9553f91d337bcb6aa", [tag: "1.0.5"]},
"telemetry": {:hex, :telemetry, "0.4.0", "8339bee3fa8b91cb84d14c2935f8ecf399ccd87301ad6da6b71c09553834b2ab", [:rebar3], [], "hexpm"},
"telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm"},
"tesla": {:hex, :tesla, "1.3.0", "f35d72f029e608f9cdc6f6d6fcc7c66cf6d6512a70cfef9206b21b8bd0203a30", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 0.4", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.3", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm"},
"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"},
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},


+ 53
- 0
priv/repo/migrations/20191128153944_fix_missing_following_count.exs View File

@@ -0,0 +1,53 @@
defmodule Pleroma.Repo.Migrations.FixMissingFollowingCount do
use Ecto.Migration

def up do
"""
UPDATE
users
SET
following_count = sub.count
FROM
(
SELECT
users.id AS sub_id
,COUNT (following_relationships.id)
FROM
following_relationships
,users
WHERE
users.id = following_relationships.follower_id
AND following_relationships.state = 'accept'
GROUP BY
users.id
) AS sub
WHERE
users.id = sub.sub_id
AND users.local = TRUE
;
"""
|> execute()

"""
UPDATE
users
SET
following_count = 0
WHERE
following_count IS NULL
"""
|> execute()

execute("ALTER TABLE users
ALTER COLUMN following_count SET DEFAULT 0,
ALTER COLUMN following_count SET NOT NULL
")
end

def down do
execute("ALTER TABLE users
ALTER COLUMN following_count DROP DEFAULT,
ALTER COLUMN following_count DROP NOT NULL
")
end
end

+ 47
- 0
test/federation/federation_test.exs View File

@@ -0,0 +1,47 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only

defmodule Pleroma.Integration.FederationTest do
use Pleroma.DataCase
@moduletag :federated
import Pleroma.Cluster

setup_all do
Pleroma.Cluster.spawn_default_cluster()
:ok
end

@federated1 :"federated1@127.0.0.1"
describe "federated cluster primitives" do
test "within/2 captures local bindings and executes block on remote node" do
captured_binding = :captured

result =
within @federated1 do
user = Pleroma.Factory.insert(:user)
{captured_binding, node(), user}
end

assert {:captured, @federated1, user} = result
refute Pleroma.User.get_by_id(user.id)
assert user.id == within(@federated1, do: Pleroma.User.get_by_id(user.id)).id
end

test "runs webserver on customized port" do
{nickname, url, url_404} =
within @federated1 do
import Pleroma.Web.Router.Helpers
user = Pleroma.Factory.insert(:user)
user_url = account_url(Pleroma.Web.Endpoint, :show, user)
url_404 = account_url(Pleroma.Web.Endpoint, :show, "not-exists")

{user.nickname, user_url, url_404}
end

assert {:ok, {{_, 200, _}, _headers, body}} = :httpc.request(~c"#{url}")
assert %{"acct" => ^nickname} = Jason.decode!(body)
assert {:ok, {{_, 404, _}, _headers, _body}} = :httpc.request(~c"#{url_404}")
end
end
end

+ 218
- 0
test/support/cluster.ex View File

@@ -0,0 +1,218 @@
defmodule Pleroma.Cluster do
@moduledoc """
Facilities for managing a cluster of slave VM's for federated testing.

## Spawning the federated cluster

`spawn_cluster/1` spawns a map of slave nodes that are started
within the running VM. During startup, the slave node is sent all configuration
from the parent node, as well as all code. After receiving configuration and
code, the slave then starts all applications currently running on the parent.
The configuration passed to `spawn_cluster/1` overrides any parent application
configuration for the provided OTP app and key. This is useful for customizing
the Ecto database, Phoenix webserver ports, etc.

For example, to start a single federated VM named ":federated1", with the
Pleroma Endpoint running on port 4123, and with a database named
"pleroma_test1", you would run:

endpoint_conf = Application.fetch_env!(:pleroma, Pleroma.Web.Endpoint)
repo_conf = Application.fetch_env!(:pleroma, Pleroma.Repo)

Pleroma.Cluster.spawn_cluster(%{
:"federated1@127.0.0.1" => [
{:pleroma, Pleroma.Repo, Keyword.merge(repo_conf, database: "pleroma_test1")},
{:pleroma, Pleroma.Web.Endpoint,
Keyword.merge(endpoint_conf, http: [port: 4011], url: [port: 4011], server: true)}
]
})

*Note*: application configuration for a given key is not merged,
so any customization requires first fetching the existing values
and merging yourself by providing the merged configuration,
such as above with the endpoint config and repo config.

## Executing code within a remote node

Use the `within/2` macro to execute code within the context of a remote
federated node. The code block captures all local variable bindings from
the parent's context and returns the result of the expression after executing
it on the remote node. For example:

import Pleroma.Cluster

parent_value = 123

result =
within :"federated1@127.0.0.1" do
{node(), parent_value}
end

assert result == {:"federated1@127.0.0.1, 123}

*Note*: while local bindings are captured and available within the block,
other parent contexts like required, aliased, or imported modules are not
in scope. Those will need to be reimported/aliases/required within the block
as `within/2` is a remote procedure call.
"""

@extra_apps Pleroma.Mixfile.application()[:extra_applications]

@doc """
Spawns the default Pleroma federated cluster.

Values before may be customized as needed for the test suite.
"""
def spawn_default_cluster do
endpoint_conf = Application.fetch_env!(:pleroma, Pleroma.Web.Endpoint)
repo_conf = Application.fetch_env!(:pleroma, Pleroma.Repo)

spawn_cluster(%{
:"federated1@127.0.0.1" => [
{:pleroma, Pleroma.Repo, Keyword.merge(repo_conf, database: "pleroma_test_federated1")},
{:pleroma, Pleroma.Web.Endpoint,
Keyword.merge(endpoint_conf, http: [port: 4011], url: [port: 4011], server: true)}
],
:"federated2@127.0.0.1" => [
{:pleroma, Pleroma.Repo, Keyword.merge(repo_conf, database: "pleroma_test_federated2")},
{:pleroma, Pleroma.Web.Endpoint,
Keyword.merge(endpoint_conf, http: [port: 4012], url: [port: 4012], server: true)}
]
})
end

@doc """
Spawns a configured map of federated nodes.

See `Pleroma.Cluster` module documentation for details.
"""
def spawn_cluster(node_configs) do
# Turn node into a distributed node with the given long name
:net_kernel.start([:"primary@127.0.0.1"])

# Allow spawned nodes to fetch all code from this node
{:ok, _} = :erl_boot_server.start([])
allow_boot("127.0.0.1")

silence_logger_warnings(fn ->
node_configs
|> Enum.map(&Task.async(fn -> start_slave(&1) end))
|> Enum.map(&Task.await(&1, 60_000))
end)
end

@doc """
Executes block of code again remote node.

See `Pleroma.Cluster` module documentation for details.
"""
defmacro within(node, do: block) do
quote do
rpc(unquote(node), unquote(__MODULE__), :eval_quoted, [
unquote(Macro.escape(block)),
binding()
])
end
end

@doc false
def eval_quoted(block, binding) do
{result, _binding} = Code.eval_quoted(block, binding, __ENV__)
result
end

defp start_slave({node_host, override_configs}) do
log(node_host, "booting federated VM")
{:ok, node} = :slave.start(~c"127.0.0.1", node_name(node_host), vm_args())
add_code_paths(node)
load_apps_and_transfer_configuration(node, override_configs)
ensure_apps_started(node)
{:ok, node}
end

def rpc(node, module, function, args) do
:rpc.block_call(node, module, function, args)
end

defp vm_args do
~c"-loader inet -hosts 127.0.0.1 -setcookie #{:erlang.get_cookie()}"
end

defp allow_boot(host) do
{:ok, ipv4} = :inet.parse_ipv4_address(~c"#{host}")
:ok = :erl_boot_server.add_slave(ipv4)
end

defp add_code_paths(node) do
rpc(node, :code, :add_paths, [:code.get_path()])
end

defp load_apps_and_transfer_configuration(node, override_configs) do
Enum.each(Application.loaded_applications(), fn {app_name, _, _} ->
app_name
|> Application.get_all_env()
|> Enum.each(fn {key, primary_config} ->
rpc(node, Application, :put_env, [app_name, key, primary_config, [persistent: true]])
end)
end)

Enum.each(override_configs, fn {app_name, key, val} ->
rpc(node, Application, :put_env, [app_name, key, val, [persistent: true]])
end)
end

defp log(node, msg), do: IO.puts("[#{node}] #{msg}")

defp ensure_apps_started(node) do
loaded_names = Enum.map(Application.loaded_applications(), fn {name, _, _} -> name end)
app_names = @extra_apps ++ (loaded_names -- @extra_apps)

rpc(node, Application, :ensure_all_started, [:mix])
rpc(node, Mix, :env, [Mix.env()])
rpc(node, __MODULE__, :prepare_database, [])

log(node, "starting application")

Enum.reduce(app_names, MapSet.new(), fn app, loaded ->
if Enum.member?(loaded, app) do
loaded
else
{:ok, started} = rpc(node, Application, :ensure_all_started, [app])
MapSet.union(loaded, MapSet.new(started))
end
end)
end

@doc false
def prepare_database do
log(node(), "preparing database")
repo_config = Application.get_env(:pleroma, Pleroma.Repo)
repo_config[:adapter].storage_down(repo_config)
repo_config[:adapter].storage_up(repo_config)

{:ok, _, _} =
Ecto.Migrator.with_repo(Pleroma.Repo, fn repo ->
Ecto.Migrator.run(repo, :up, log: false, all: true)
end)

Ecto.Adapters.SQL.Sandbox.mode(Pleroma.Repo, :manual)
{:ok, _} = Application.ensure_all_started(:ex_machina)
end

defp silence_logger_warnings(func) do
prev_level = Logger.level()
Logger.configure(level: :error)
res = func.()
Logger.configure(level: prev_level)

res
end

defp node_name(node_host) do
node_host
|> to_string()
|> String.split("@")
|> Enum.at(0)
|> String.to_atom()
end
end

+ 2
- 1
test/test_helper.exs View File

@@ -3,7 +3,8 @@
# SPDX-License-Identifier: AGPL-3.0-only

os_exclude = if :os.type() == {:unix, :darwin}, do: [skip_on_mac: true], else: []
ExUnit.start(exclude: os_exclude)
ExUnit.start(exclude: [:federated | os_exclude])

Ecto.Adapters.SQL.Sandbox.mode(Pleroma.Repo, :manual)
Mox.defmock(Pleroma.ReverseProxy.ClientMock, for: Pleroma.ReverseProxy.Client)
{:ok, _} = Application.ensure_all_started(:ex_machina)


+ 16
- 62
test/user_test.exs View File

@@ -1011,9 +1011,9 @@ defmodule Pleroma.UserTest do
{:ok, user} = User.follow(user, user2)
{:ok, _user} = User.deactivate(user)

info = User.get_cached_user_info(user2)
user2 = User.get_cached_by_id(user2.id)

assert info.follower_count == 0
assert user2.follower_count == 0
assert [] = User.get_followers(user2)
end

@@ -1027,10 +1027,10 @@ defmodule Pleroma.UserTest do

{:ok, _user} = User.deactivate(user)

info = User.get_cached_user_info(user2)
user2 = User.get_cached_by_id(user2.id)

assert refresh_record(user2).following_count == 0
assert info.following_count == 0
assert user2.following_count == 0
assert User.following_count(user2) == 0
assert [] = User.get_friends(user2)
end
@@ -1232,13 +1232,12 @@ defmodule Pleroma.UserTest do
describe "caching" do
test "invalidate_cache works" do
user = insert(:user)
_user_info = User.get_cached_user_info(user)

User.set_cache(user)
User.invalidate_cache(user)

{:ok, nil} = Cachex.get(:user_cache, "ap_id:#{user.ap_id}")
{:ok, nil} = Cachex.get(:user_cache, "nickname:#{user.nickname}")
{:ok, nil} = Cachex.get(:user_cache, "user_info:#{user.id}")
end

test "User.delete() plugs any possible zombie objects" do
@@ -1395,7 +1394,7 @@ defmodule Pleroma.UserTest do
{:ok, _user_relationship} = User.block(user, follower)
user = refresh_record(user)

assert User.user_info(user).follower_count == 2
assert user.follower_count == 2
end

describe "list_inactive_users_query/1" do
@@ -1572,51 +1571,6 @@ defmodule Pleroma.UserTest do
end
end

describe "set_info_cache/2" do
setup do
user = insert(:user)
{:ok, user: user}
end

test "update from args", %{user: user} do
User.set_info_cache(user, %{following_count: 15, follower_count: 18})

%{follower_count: followers, following_count: following} = User.get_cached_user_info(user)
assert followers == 18
assert following == 15
end

test "without args", %{user: user} do
User.set_info_cache(user, %{})

%{follower_count: followers, following_count: following} = User.get_cached_user_info(user)
assert followers == 0
assert following == 0
end
end

describe "user_info/2" do
setup do
user = insert(:user)
{:ok, user: user}
end

test "update from args", %{user: user} do
%{follower_count: followers, following_count: following} =
User.user_info(user, %{following_count: 15, follower_count: 18})

assert followers == 18
assert following == 15
end

test "without args", %{user: user} do
%{follower_count: followers, following_count: following} = User.user_info(user)

assert followers == 0
assert following == 0
end
end

describe "is_internal_user?/1" do
test "non-internal user returns false" do
user = insert(:user)
@@ -1673,14 +1627,14 @@ defmodule Pleroma.UserTest do
ap_enabled: true
)

assert User.user_info(other_user).following_count == 0
assert User.user_info(other_user).follower_count == 0
assert other_user.following_count == 0
assert other_user.follower_count == 0

{:ok, user} = Pleroma.User.follow(user, other_user)
other_user = Pleroma.User.get_by_id(other_user.id)

assert User.user_info(user).following_count == 1
assert User.user_info(other_user).follower_count == 1
assert user.following_count == 1
assert other_user.follower_count == 1
end

test "syncronizes the counters with the remote instance for the followed when enabled" do
@@ -1696,14 +1650,14 @@ defmodule Pleroma.UserTest do
ap_enabled: true
)

assert User.user_info(other_user).following_count == 0
assert User.user_info(other_user).follower_count == 0
assert other_user.following_count == 0
assert other_user.follower_count == 0

Pleroma.Config.put([:instance, :external_user_synchronization], true)
{:ok, _user} = User.follow(user, other_user)
other_user = User.get_by_id(other_user.id)

assert User.user_info(other_user).follower_count == 437
assert other_user.follower_count == 437
end

test "syncronizes the counters with the remote instance for the follower when enabled" do
@@ -1719,13 +1673,13 @@ defmodule Pleroma.UserTest do
ap_enabled: true
)

assert User.user_info(other_user).following_count == 0
assert User.user_info(other_user).follower_count == 0
assert other_user.following_count == 0
assert other_user.follower_count == 0

Pleroma.Config.put([:instance, :external_user_synchronization], true)
{:ok, other_user} = User.follow(other_user, user)

assert User.user_info(other_user).following_count == 152
assert other_user.following_count == 152
end
end



+ 5
- 0
test/web/activity_pub/transmogrifier_test.exs View File

@@ -39,6 +39,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
assert activity == returned_activity
end

@tag capture_log: true
test "it fetches replied-to activities if we don't have them" do
data =
File.read!("test/fixtures/mastodon-post-activity.json")
@@ -533,6 +534,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
assert object.data["content"] == "this is a private toot"
end

@tag capture_log: true
test "it rejects incoming announces with an inlined activity from another origin" do
data =
File.read!("test/fixtures/bogus-mastodon-announce.json")
@@ -814,6 +816,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
assert Activity.get_by_id(activity.id)
end

@tag capture_log: true
test "it works for incoming user deletes" do
%{ap_id: ap_id} = insert(:user, ap_id: "http://mastodon.example.org/users/admin")

@@ -1749,6 +1752,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
assert modified_object["inReplyToAtomUri"] == ""
end

@tag capture_log: true
test "returns modified object when allowed incoming reply", %{data: data} do
object_with_reply =
Map.put(
@@ -1868,6 +1872,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
end) =~ "Unsupported URI scheme"
end

@tag capture_log: true
test "returns {:ok, %Object{}} for success case" do
assert {:ok, %Object{}} =
Transmogrifier.get_obj_helper("https://shitposter.club/notice/2827873")


+ 1
- 0
test/web/admin_api/admin_api_controller_test.exs View File

@@ -1923,6 +1923,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
Pleroma.Config.put([:instance, :dynamic_configuration], true)
end

@tag capture_log: true
test "create new config setting in db", %{conn: conn} do
conn =
post(conn, "/api/pleroma/admin/config", %{


+ 10
- 1
test/web/mastodon_api/views/account_view_test.exs View File

@@ -350,7 +350,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
}
}

assert expected == AccountView.render("show.json", %{user: user, for: other_user})
assert expected ==
AccountView.render("show.json", %{user: refresh_record(user), for: other_user})
end

test "returns the settings store if the requesting user is the represented user and it's requested specifically" do
@@ -374,6 +375,14 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
refute result.display_name == "<marquee> username </marquee>"
end

test "never display nil user follow counts" do
user = insert(:user, following_count: 0, follower_count: 0)
result = AccountView.render("show.json", %{user: user})

assert result.following_count == 0
assert result.followers_count == 0
end

describe "hiding follows/following" do
test "shows when follows/followers stats are hidden and sets follow/follower count to 0" do
user =


+ 1
- 1
test/web/streamer/streamer_test.exs View File

@@ -15,7 +15,7 @@ defmodule Pleroma.Web.StreamerTest do
alias Pleroma.Web.Streamer.StreamerSocket
alias Pleroma.Web.Streamer.Worker

@moduletag needs_streamer: true
@moduletag needs_streamer: true, capture_log: true
clear_config_all([:instance, :skip_thread_containment])

describe "user streams" do


Loading…
Cancel
Save