# Conflicts: # mix.lockfeature/compile_get
@@ -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: | |||
@@ -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 | |||
# | |||
@@ -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 |
@@ -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 | |||
@@ -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) | |||
@@ -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 | |||
@@ -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!() | |||
@@ -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"}, | |||
@@ -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"}, | |||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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) | |||
@@ -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 | |||
@@ -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") | |||
@@ -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", %{ | |||
@@ -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 = | |||
@@ -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 | |||