Merge branch 'develop' into fix/dymamic-docs

This commit is contained in:
rinpatch 2020-02-06 15:04:21 +03:00
commit 6769ecd948
18 changed files with 312 additions and 99 deletions

View File

@ -9,6 +9,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- **Breaking**: OStatus protocol support - **Breaking**: OStatus protocol support
- **Breaking**: MDII uploader - **Breaking**: MDII uploader
- **Breaking**: Using third party engines for user recommendation - **Breaking**: Using third party engines for user recommendation
<details>
<summary>API Changes</summary>
- **Breaking**: AdminAPI: migrate_from_db endpoint
</details>
### Changed ### Changed
- **Breaking:** Pleroma won't start if it detects unapplied migrations - **Breaking:** Pleroma won't start if it detects unapplied migrations

View File

@ -509,7 +509,6 @@
config :auto_linker, config :auto_linker,
opts: [ opts: [
scheme: true,
extra: true, extra: true,
# TODO: Set to :no_scheme when it works properly # TODO: Set to :no_scheme when it works properly
validate_tld: true, validate_tld: true,

View File

@ -2180,11 +2180,6 @@
type: :boolean, type: :boolean,
description: "Set to `false` to remove target='_blank' attribute" description: "Set to `false` to remove target='_blank' attribute"
}, },
%{
key: :scheme,
type: :boolean,
description: "Set to `true` to link urls with schema http://google.com"
},
%{ %{
key: :truncate, key: :truncate,
type: [:integer, false], type: [:integer, false],

View File

@ -665,11 +665,9 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
- 404 Not Found `"Not found"` - 404 Not Found `"Not found"`
- On success: 200 OK `{}` - On success: 200 OK `{}`
## `GET /api/pleroma/admin/config/migrate_from_db` ## `GET /api/pleroma/admin/restart`
### Run mix task pleroma.config migrate_from_db ### Restarts pleroma application
Copies all settings from database to `config/{env}.exported_from_db.secret.exs` with deletion from the table. Where `{env}` is the environment in which `pleroma` is running.
- Params: none - Params: none
- Response: - Response:

View File

@ -3,8 +3,6 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Config.Loader do defmodule Pleroma.Config.Loader do
@paths ["config/config.exs", "config/#{Mix.env()}.exs"]
@reject_keys [ @reject_keys [
Pleroma.Repo, Pleroma.Repo,
Pleroma.Web.Endpoint, Pleroma.Web.Endpoint,
@ -35,8 +33,8 @@ defp do_merge(conf1, conf2), do: Mix.Config.merge(conf1, conf2)
def load_and_merge do def load_and_merge do
all_paths = all_paths =
if Pleroma.Config.get(:release), if Pleroma.Config.get(:release),
do: @paths ++ ["config/releases.exs"], do: ["config/config.exs", "config/releases.exs"],
else: @paths else: ["config/config.exs"]
all_paths all_paths
|> Enum.map(&load(&1)) |> Enum.map(&load(&1))

View File

@ -10,6 +10,30 @@ defmodule Pleroma.Config.TransferTask do
require Logger require Logger
@type env() :: :test | :benchmark | :dev | :prod
@reboot_time_keys [
{:pleroma, :hackney_pools},
{:pleroma, :chat},
{:pleroma, Oban},
{:pleroma, :rate_limit},
{:pleroma, :markup},
{:plerome, :streamer}
]
@reboot_time_subkeys [
{:pleroma, Pleroma.Captcha, [:seconds_valid]},
{:pleroma, Pleroma.Upload, [:proxy_remote]},
{:pleroma, :instance, [:upload_limit]},
{:pleroma, :email_notifications, [:digest]},
{:pleroma, :oauth2, [:clean_expired_tokens]},
{:pleroma, Pleroma.ActivityExpiration, [:enabled]},
{:pleroma, Pleroma.ScheduledActivity, [:enabled]},
{:pleroma, :gopher, [:enabled]}
]
@reject [nil, :prometheus]
def start_link(_) do def start_link(_) do
load_and_update_env() load_and_update_env()
if Pleroma.Config.get(:env) == :test, do: Ecto.Adapters.SQL.Sandbox.checkin(Repo) if Pleroma.Config.get(:env) == :test, do: Ecto.Adapters.SQL.Sandbox.checkin(Repo)
@ -17,21 +41,34 @@ def start_link(_) do
end end
@spec load_and_update_env([ConfigDB.t()]) :: :ok | false @spec load_and_update_env([ConfigDB.t()]) :: :ok | false
def load_and_update_env(deleted \\ []) do def load_and_update_env(deleted \\ [], restart_pleroma? \\ true) do
with true <- Pleroma.Config.get(:configurable_from_database), with true <- Pleroma.Config.get(:configurable_from_database),
true <- Ecto.Adapters.SQL.table_exists?(Repo, "config"), true <- Ecto.Adapters.SQL.table_exists?(Repo, "config"),
started_applications <- Application.started_applications() do started_applications <- Application.started_applications() do
# We need to restart applications for loaded settings take effect # We need to restart applications for loaded settings take effect
in_db = Repo.all(ConfigDB) in_db = Repo.all(ConfigDB)
with_deleted = in_db ++ deleted with_deleted = in_db ++ deleted
reject_for_restart = if restart_pleroma?, do: @reject, else: [:pleroma | @reject]
applications =
with_deleted with_deleted
|> Enum.map(&merge_and_update(&1)) |> Enum.map(&merge_and_update(&1))
|> Enum.uniq() |> Enum.uniq()
# TODO: some problem with prometheus after restart! # TODO: some problem with prometheus after restart!
|> Enum.reject(&(&1 in [:pleroma, nil, :prometheus])) |> Enum.reject(&(&1 in reject_for_restart))
|> Enum.each(&restart(started_applications, &1))
# to be ensured that pleroma will be restarted last
applications =
if :pleroma in applications do
List.delete(applications, :pleroma) ++ [:pleroma]
else
applications
end
Enum.each(applications, &restart(started_applications, &1, Pleroma.Config.get(:env)))
:ok :ok
end end
@ -43,12 +80,25 @@ defp merge_and_update(setting) do
group = ConfigDB.from_string(setting.group) group = ConfigDB.from_string(setting.group)
default = Pleroma.Config.Holder.config(group, key) default = Pleroma.Config.Holder.config(group, key)
merged_value = merge_value(setting, default, group, key) value = ConfigDB.from_binary(setting.value)
merged_value =
if Ecto.get_meta(setting, :state) == :deleted do
default
else
if can_be_merged?(default, value) do
ConfigDB.merge_group(group, key, default, value)
else
value
end
end
:ok = update_env(group, key, merged_value) :ok = update_env(group, key, merged_value)
if group != :logger do if group != :logger do
if group != :pleroma or pleroma_need_restart?(group, key, value) do
group group
end
else else
# change logger configuration in runtime, without restart # change logger configuration in runtime, without restart
if Keyword.keyword?(merged_value) and if Keyword.keyword?(merged_value) and
@ -76,22 +126,31 @@ defp merge_and_update(setting) do
end end
end end
defp merge_value(%{__meta__: %{state: :deleted}}, default, _group, _key), do: default @spec pleroma_need_restart?(atom(), atom(), any()) :: boolean()
def pleroma_need_restart?(group, key, value) do
defp merge_value(setting, default, group, key) do group_and_key_need_reboot?(group, key) or group_and_subkey_need_reboot?(group, key, value)
value = ConfigDB.from_binary(setting.value)
if can_be_merged?(default, value) do
ConfigDB.merge_group(group, key, default, value)
else
value
end end
defp group_and_key_need_reboot?(group, key) do
Enum.any?(@reboot_time_keys, fn {g, k} -> g == group and k == key end)
end
defp group_and_subkey_need_reboot?(group, key, value) do
Keyword.keyword?(value) and
Enum.any?(@reboot_time_subkeys, fn {g, k, subkeys} ->
g == group and k == key and
Enum.any?(Keyword.keys(value), &(&1 in subkeys))
end)
end end
defp update_env(group, key, nil), do: Application.delete_env(group, key) defp update_env(group, key, nil), do: Application.delete_env(group, key)
defp update_env(group, key, value), do: Application.put_env(group, key, value) defp update_env(group, key, value), do: Application.put_env(group, key, value)
defp restart(started_applications, app) do defp restart(_, :pleroma, :test), do: Logger.warn("pleroma restarted")
defp restart(_, :pleroma, _), do: send(Restarter.Pleroma, :after_boot)
defp restart(started_applications, app, _) do
with {^app, _, _} <- List.keyfind(started_applications, app, 0), with {^app, _, _} <- List.keyfind(started_applications, app, 0),
:ok <- Application.stop(app) do :ok <- Application.stop(app) do
:ok = Application.start(app) :ok = Application.start(app)

View File

@ -13,7 +13,8 @@ defmodule Pleroma.Formatter do
@auto_linker_config hashtag: true, @auto_linker_config hashtag: true,
hashtag_handler: &Pleroma.Formatter.hashtag_handler/4, hashtag_handler: &Pleroma.Formatter.hashtag_handler/4,
mention: true, mention: true,
mention_handler: &Pleroma.Formatter.mention_handler/4 mention_handler: &Pleroma.Formatter.mention_handler/4,
scheme: true
def escape_mention_handler("@" <> nickname = mention, buffer, _, _) do def escape_mention_handler("@" <> nickname = mention, buffer, _, _) do
case User.get_cached_by_nickname(nickname) do case User.get_cached_by_nickname(nickname) do

View File

@ -97,7 +97,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
plug( plug(
OAuthScopesPlug, OAuthScopesPlug,
%{scopes: ["read"], admin: true} %{scopes: ["read"], admin: true}
when action in [:config_show, :migrate_from_db, :list_log] when action in [:config_show, :list_log]
) )
plug( plug(
@ -793,19 +793,6 @@ def config_descriptions(conn, _params) do
|> Plug.Conn.send_resp(200, @descriptions_json) |> Plug.Conn.send_resp(200, @descriptions_json)
end end
def migrate_from_db(conn, _params) do
with :ok <- configurable_from_database(conn) do
Mix.Tasks.Pleroma.Config.run([
"migrate_from_db",
"--env",
to_string(Pleroma.Config.get(:env)),
"-d"
])
json(conn, %{})
end
end
def config_show(conn, %{"only_db" => true}) do def config_show(conn, %{"only_db" => true}) do
with :ok <- configurable_from_database(conn) do with :ok <- configurable_from_database(conn) do
configs = Pleroma.Repo.all(ConfigDB) configs = Pleroma.Repo.all(ConfigDB)
@ -890,17 +877,36 @@ def config_update(conn, %{"configs" => configs}) do
Ecto.get_meta(config, :state) == :deleted Ecto.get_meta(config, :state) == :deleted
end) end)
Pleroma.Config.TransferTask.load_and_update_env(deleted) Pleroma.Config.TransferTask.load_and_update_env(deleted, false)
Mix.Tasks.Pleroma.Config.run([ need_reboot? =
"migrate_from_db", Enum.any?(updated, fn config ->
"--env", group = ConfigDB.from_string(config.group)
to_string(Pleroma.Config.get(:env)) key = ConfigDB.from_string(config.key)
]) value = ConfigDB.from_binary(config.value)
Pleroma.Config.TransferTask.pleroma_need_restart?(group, key, value)
end)
response = %{configs: updated}
response =
if need_reboot?, do: Map.put(response, :need_reboot, need_reboot?), else: response
conn conn
|> put_view(ConfigView) |> put_view(ConfigView)
|> render("index.json", %{configs: updated}) |> render("index.json", response)
end
end
def restart(conn, _params) do
with :ok <- configurable_from_database(conn) do
if Pleroma.Config.get(:env) == :test do
Logger.warn("pleroma restarted")
else
send(Restarter.Pleroma, {:restart, 50})
end
json(conn, %{})
end end
end end

View File

@ -5,10 +5,16 @@
defmodule Pleroma.Web.AdminAPI.ConfigView do defmodule Pleroma.Web.AdminAPI.ConfigView do
use Pleroma.Web, :view use Pleroma.Web, :view
def render("index.json", %{configs: configs}) do def render("index.json", %{configs: configs} = params) do
%{ map = %{
configs: render_many(configs, __MODULE__, "show.json", as: :config) configs: render_many(configs, __MODULE__, "show.json", as: :config)
} }
if params[:need_reboot] do
Map.put(map, :need_reboot, true)
else
map
end
end end
def render("show.json", %{config: config}) do def render("show.json", %{config: config}) do

View File

@ -315,8 +315,9 @@ def pin(id_or_ap_id, %{ap_id: user_ap_id} = user) do
with %Activity{ with %Activity{
actor: ^user_ap_id, actor: ^user_ap_id,
data: %{"type" => "Create"}, data: %{"type" => "Create"},
object: %Object{data: %{"type" => "Note"}} object: %Object{data: %{"type" => object_type}}
} = activity <- get_by_id_or_ap_id(id_or_ap_id), } = activity <- get_by_id_or_ap_id(id_or_ap_id),
true <- object_type in ["Note", "Article", "Question"],
true <- Visibility.is_public?(activity), true <- Visibility.is_public?(activity),
{:ok, _user} <- User.add_pinnned_activity(user, activity) do {:ok, _user} <- User.add_pinnned_activity(user, activity) do
{:ok, activity} {:ok, activity}

View File

@ -196,7 +196,7 @@ defmodule Pleroma.Web.Router do
get("/config", AdminAPIController, :config_show) get("/config", AdminAPIController, :config_show)
post("/config", AdminAPIController, :config_update) post("/config", AdminAPIController, :config_update)
get("/config/descriptions", AdminAPIController, :config_descriptions) get("/config/descriptions", AdminAPIController, :config_descriptions)
get("/config/migrate_from_db", AdminAPIController, :migrate_from_db) get("/restart", AdminAPIController, :restart)
get("/moderation_log", AdminAPIController, :list_log) get("/moderation_log", AdminAPIController, :list_log)

10
mix.exs
View File

@ -8,7 +8,7 @@ def project do
elixir: "~> 1.8", elixir: "~> 1.8",
elixirc_paths: elixirc_paths(Mix.env()), elixirc_paths: elixirc_paths(Mix.env()),
compilers: [:phoenix, :gettext] ++ Mix.compilers(), compilers: [:phoenix, :gettext] ++ Mix.compilers(),
elixirc_options: [warnings_as_errors: true], elixirc_options: [warnings_as_errors: warnings_as_errors(Mix.env())],
xref: [exclude: [:eldap]], xref: [exclude: [:eldap]],
start_permanent: Mix.env() == :prod, start_permanent: Mix.env() == :prod,
aliases: aliases(), aliases: aliases(),
@ -73,6 +73,11 @@ defp elixirc_paths(:benchmark), do: ["lib", "benchmarks"]
defp elixirc_paths(:test), do: ["lib", "test/support"] defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"] defp elixirc_paths(_), do: ["lib"]
defp warnings_as_errors(:prod), do: false
# Uncomment this if you need testing configurable_from_database logic
# defp warnings_as_errors(:dev), do: false
defp warnings_as_errors(_), do: true
# Specifies OAuth dependencies. # Specifies OAuth dependencies.
defp oauth_deps do defp oauth_deps do
oauth_strategy_packages = oauth_strategy_packages =
@ -166,7 +171,8 @@ defp deps do
{:captcha, {:captcha,
git: "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", git: "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git",
ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"}, ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"},
{:mox, "~> 0.5", only: :test} {:mox, "~> 0.5", only: :test},
{:restarter, path: "./restarter"}
] ++ oauth_deps() ] ++ oauth_deps()
end end

28
restarter/lib/pleroma.ex Normal file
View File

@ -0,0 +1,28 @@
defmodule Restarter.Pleroma do
use GenServer
def start_link(_) do
GenServer.start_link(__MODULE__, [], name: __MODULE__)
end
def init(_), do: {:ok, %{}}
def handle_info(:after_boot, %{after_boot: true} = state), do: {:noreply, state}
def handle_info(:after_boot, state) do
restart(:pleroma)
{:noreply, Map.put(state, :after_boot, true)}
end
def handle_info({:restart, delay}, state) do
Process.sleep(delay)
restart(:pleroma)
{:noreply, state}
end
defp restart(app) do
:ok = Application.ensure_started(app)
:ok = Application.stop(app)
:ok = Application.start(app)
end
end

View File

@ -0,0 +1,8 @@
defmodule Restarter do
use Application
def start(_, _) do
opts = [strategy: :one_for_one, name: Restarter.Supervisor]
Supervisor.start_link([Restarter.Pleroma], opts)
end
end

21
restarter/mix.exs Normal file
View File

@ -0,0 +1,21 @@
defmodule Restarter.MixProject do
use Mix.Project
def project do
[
app: :restarter,
version: "0.1.0",
elixir: "~> 1.8",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
def application do
[
mod: {Restarter, []}
]
end
defp deps, do: []
end

View File

@ -5,6 +5,8 @@
defmodule Pleroma.Config.TransferTaskTest do defmodule Pleroma.Config.TransferTaskTest do
use Pleroma.DataCase use Pleroma.DataCase
import ExUnit.CaptureLog
alias Pleroma.Config.TransferTask alias Pleroma.Config.TransferTask
alias Pleroma.ConfigDB alias Pleroma.ConfigDB
@ -105,4 +107,75 @@ test "transfer config values with full subkey update" do
Application.put_env(:pleroma, :assets, assets) Application.put_env(:pleroma, :assets, assets)
end) end)
end end
describe "pleroma restart" do
test "don't restart if no reboot time settings were changed" do
emoji = Application.get_env(:pleroma, :emoji)
on_exit(fn -> Application.put_env(:pleroma, :emoji, emoji) end)
ConfigDB.create(%{
group: ":pleroma",
key: ":emoji",
value: [groups: [a: 1, b: 2]]
})
refute String.contains?(
capture_log(fn -> TransferTask.start_link([]) end),
"pleroma restarted"
)
end
test "restart pleroma on reboot time key" do
chat = Application.get_env(:pleroma, :chat)
on_exit(fn -> Application.put_env(:pleroma, :chat, chat) end)
ConfigDB.create(%{
group: ":pleroma",
key: ":chat",
value: [enabled: false]
})
assert capture_log(fn -> TransferTask.start_link([]) end) =~ "pleroma restarted"
end
test "restart pleroma on reboot time subkey" do
captcha = Application.get_env(:pleroma, Pleroma.Captcha)
on_exit(fn -> Application.put_env(:pleroma, Pleroma.Captcha, captcha) end)
ConfigDB.create(%{
group: ":pleroma",
key: "Pleroma.Captcha",
value: [seconds_valid: 60]
})
assert capture_log(fn -> TransferTask.start_link([]) end) =~ "pleroma restarted"
end
test "don't restart pleroma on reboot time key and subkey if there is false flag" do
chat = Application.get_env(:pleroma, :chat)
captcha = Application.get_env(:pleroma, Pleroma.Captcha)
on_exit(fn ->
Application.put_env(:pleroma, :chat, chat)
Application.put_env(:pleroma, Pleroma.Captcha, captcha)
end)
ConfigDB.create(%{
group: ":pleroma",
key: ":chat",
value: [enabled: false]
})
ConfigDB.create(%{
group: ":pleroma",
key: "Pleroma.Captcha",
value: [seconds_valid: 60]
})
refute String.contains?(
capture_log(fn -> TransferTask.load_and_update_env([], false) end),
"pleroma restarted"
)
end
end
end end

View File

@ -2043,7 +2043,6 @@ test "POST /api/pleroma/admin/config error", %{conn: conn} do
Application.delete_env(:pleroma, Pleroma.Captcha.NotReal) Application.delete_env(:pleroma, Pleroma.Captcha.NotReal)
Application.put_env(:pleroma, :http, http) Application.put_env(:pleroma, :http, http)
Application.put_env(:tesla, :adapter, Tesla.Mock) Application.put_env(:tesla, :adapter, Tesla.Mock)
:ok = File.rm("config/test.exported_from_db.secret.exs")
end) end)
end end
@ -2170,7 +2169,7 @@ test "create new config setting in db", %{conn: conn} do
assert Application.get_env(:idna, :key5) == {"string", Pleroma.Captcha.NotReal, []} assert Application.get_env(:idna, :key5) == {"string", Pleroma.Captcha.NotReal, []}
end end
test "save config setting without key", %{conn: conn} do test "save configs setting without explicit key", %{conn: conn} do
level = Application.get_env(:quack, :level) level = Application.get_env(:quack, :level)
meta = Application.get_env(:quack, :meta) meta = Application.get_env(:quack, :meta)
webhook_url = Application.get_env(:quack, :webhook_url) webhook_url = Application.get_env(:quack, :webhook_url)
@ -2256,6 +2255,34 @@ test "saving config with partial update", %{conn: conn} do
} }
end end
test "saving config which need pleroma reboot", %{conn: conn} do
chat = Pleroma.Config.get(:chat)
on_exit(fn -> Pleroma.Config.put(:chat, chat) end)
conn =
post(
conn,
"/api/pleroma/admin/config",
%{
configs: [
%{group: ":pleroma", key: ":chat", value: [%{"tuple" => [":enabled", true]}]}
]
}
)
assert json_response(conn, 200) == %{
"configs" => [
%{
"db" => [":enabled"],
"group" => ":pleroma",
"key" => ":chat",
"value" => [%{"tuple" => [":enabled", true]}]
}
],
"need_reboot" => true
}
end
test "saving config with nested merge", %{conn: conn} do test "saving config with nested merge", %{conn: conn} do
config = config =
insert(:config, key: ":key1", value: :erlang.term_to_binary(key1: 1, key2: [k1: 1, k2: 2])) insert(:config, key: ":key1", value: :erlang.term_to_binary(key1: 1, key2: [k1: 1, k2: 2]))
@ -2957,47 +2984,15 @@ test "proxy tuple ip", %{conn: conn} do
end end
end end
describe "config mix tasks run" do describe "GET /api/pleroma/admin/restart" do
setup do
Mix.shell(Mix.Shell.Quiet)
on_exit(fn ->
Mix.shell(Mix.Shell.IO)
end)
:ok
end
clear_config(:configurable_from_database) do clear_config(:configurable_from_database) do
Pleroma.Config.put(:configurable_from_database, true) Pleroma.Config.put(:configurable_from_database, true)
end end
clear_config([:feed, :post_title]) do test "pleroma restarts", %{conn: conn} do
Pleroma.Config.put([:feed, :post_title], %{max_length: 100, omission: ""}) ExUnit.CaptureLog.capture_log(fn ->
end assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) == %{}
end) =~ "pleroma restarted"
test "transfer settings to DB and to file", %{conn: conn} do
assert Repo.all(Pleroma.ConfigDB) == []
Mix.Tasks.Pleroma.Config.migrate_to_db("test/fixtures/config/temp.secret.exs")
assert Repo.aggregate(Pleroma.ConfigDB, :count, :id) > 0
conn = get(conn, "/api/pleroma/admin/config/migrate_from_db")
assert json_response(conn, 200) == %{}
assert Repo.all(Pleroma.ConfigDB) == []
end
test "returns error if configuration from database is off", %{conn: conn} do
initial = Pleroma.Config.get(:configurable_from_database)
on_exit(fn -> Pleroma.Config.put(:configurable_from_database, initial) end)
Pleroma.Config.put(:configurable_from_database, false)
conn = get(conn, "/api/pleroma/admin/config/migrate_from_db")
assert json_response(conn, 400) ==
"To use this endpoint you need to enable configuration from database."
assert Repo.all(Pleroma.ConfigDB) == []
end end
end end

View File

@ -324,6 +324,21 @@ test "pin status", %{user: user, activity: activity} do
assert %User{pinned_activities: [^id]} = user assert %User{pinned_activities: [^id]} = user
end end
test "pin poll", %{user: user} do
{:ok, activity} =
CommonAPI.post(user, %{
"status" => "How is fediverse today?",
"poll" => %{"options" => ["Absolutely outstanding", "Not good"], "expires_in" => 20}
})
assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
id = activity.id
user = refresh_record(user)
assert %User{pinned_activities: [^id]} = user
end
test "unlisted statuses can be pinned", %{user: user} do test "unlisted statuses can be pinned", %{user: user} do
{:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!", "visibility" => "unlisted"}) {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!", "visibility" => "unlisted"})
assert {:ok, ^activity} = CommonAPI.pin(activity.id, user) assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)