Page MenuHomePhorge

No OneTemporary

Size
232 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex
index 14ce4b07d..ee7b37aab 100644
--- a/lib/pleroma/notification.ex
+++ b/lib/pleroma/notification.ex
@@ -1,262 +1,262 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Notification do
use Ecto.Schema
alias Pleroma.Activity
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Pagination
alias Pleroma.Repo
alias Pleroma.User
- alias Pleroma.Web.CommonAPI
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.Push
alias Pleroma.Web.Streamer
import Ecto.Query
import Ecto.Changeset
schema "notifications" do
field(:seen, :boolean, default: false)
belongs_to(:user, User, type: Pleroma.FlakeId)
belongs_to(:activity, Activity, type: Pleroma.FlakeId)
timestamps()
end
def changeset(%Notification{} = notification, attrs) do
notification
|> cast(attrs, [:seen])
end
- def for_user_query(user) do
- Notification
- |> where(user_id: ^user.id)
- |> where(
- [n, a],
- fragment(
- "? not in (SELECT ap_id FROM users WHERE info->'deactivated' @> 'true')",
- a.actor
- )
- )
- |> where([n, a], a.actor not in ^user.info.muted_notifications)
- |> join(:inner, [n], activity in assoc(n, :activity))
- |> join(:left, [n, a], object in Object,
- on:
+ def for_user_query(user, opts) do
+ query =
+ Notification
+ |> where(user_id: ^user.id)
+ |> where(
+ [n, a],
fragment(
- "(?->>'id') = COALESCE((? -> 'object'::text) ->> 'id'::text)",
- object.data,
- a.data
+ "? not in (SELECT ap_id FROM users WHERE info->'deactivated' @> 'true')",
+ a.actor
)
- )
- |> preload([n, a, o], activity: {a, object: o})
+ )
+ |> join(:inner, [n], activity in assoc(n, :activity))
+ |> join(:left, [n, a], object in Object,
+ on:
+ fragment(
+ "(?->>'id') = COALESCE((? -> 'object'::text) ->> 'id'::text)",
+ object.data,
+ a.data
+ )
+ )
+ |> preload([n, a, o], activity: {a, object: o})
+
+ if opts[:with_muted] do
+ query
+ else
+ where(query, [n, a], a.actor not in ^user.info.muted_notifications)
+ |> where([n, a], a.actor not in ^user.info.blocks)
+ |> where(
+ [n, a],
+ fragment("substring(? from '.*://([^/]*)')", a.actor) not in ^user.info.domain_blocks
+ )
+ |> join(:left, [n, a], tm in Pleroma.ThreadMute,
+ on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data)
+ )
+ |> where([n, a, o, tm], is_nil(tm.id))
+ end
end
def for_user(user, opts \\ %{}) do
user
- |> for_user_query()
+ |> for_user_query(opts)
|> Pagination.fetch_paginated(opts)
end
def set_read_up_to(%{id: user_id} = _user, id) do
query =
from(
n in Notification,
where: n.user_id == ^user_id,
where: n.id <= ^id,
update: [
set: [seen: true]
]
)
Repo.update_all(query, [])
end
def read_one(%User{} = user, notification_id) do
with {:ok, %Notification{} = notification} <- get(user, notification_id) do
notification
|> changeset(%{seen: true})
|> Repo.update()
end
end
def get(%{id: user_id} = _user, id) do
query =
from(
n in Notification,
where: n.id == ^id,
join: activity in assoc(n, :activity),
preload: [activity: activity]
)
notification = Repo.one(query)
case notification do
%{user_id: ^user_id} ->
{:ok, notification}
_ ->
{:error, "Cannot get notification"}
end
end
def clear(user) do
from(n in Notification, where: n.user_id == ^user.id)
|> Repo.delete_all()
end
def destroy_multiple(%{id: user_id} = _user, ids) do
from(n in Notification,
where: n.id in ^ids,
where: n.user_id == ^user_id
)
|> Repo.delete_all()
end
def dismiss(%{id: user_id} = _user, id) do
notification = Repo.get(Notification, id)
case notification do
%{user_id: ^user_id} ->
Repo.delete(notification)
_ ->
{:error, "Cannot dismiss notification"}
end
end
def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = activity) do
object = Object.normalize(activity)
unless object && object.data["type"] == "Answer" do
users = get_notified_from_activity(activity)
notifications = Enum.map(users, fn user -> create_notification(activity, user) end)
{:ok, notifications}
else
{:ok, []}
end
end
def create_notifications(%Activity{data: %{"to" => _, "type" => type}} = activity)
when type in ["Like", "Announce", "Follow"] do
users = get_notified_from_activity(activity)
notifications = Enum.map(users, fn user -> create_notification(activity, user) end)
{:ok, notifications}
end
def create_notifications(_), do: {:ok, []}
# TODO move to sql, too.
def create_notification(%Activity{} = activity, %User{} = user) do
unless skip?(activity, user) do
notification = %Notification{user_id: user.id, activity: activity}
{:ok, notification} = Repo.insert(notification)
Streamer.stream("user", notification)
Streamer.stream("user:notification", notification)
Push.send(notification)
notification
end
end
def get_notified_from_activity(activity, local_only \\ true)
def get_notified_from_activity(
%Activity{data: %{"to" => _, "type" => type} = _data} = activity,
local_only
)
when type in ["Create", "Like", "Announce", "Follow"] do
recipients =
[]
|> Utils.maybe_notify_to_recipients(activity)
|> Utils.maybe_notify_mentioned_recipients(activity)
|> Utils.maybe_notify_subscribers(activity)
|> Enum.uniq()
User.get_users_from_set(recipients, local_only)
end
def get_notified_from_activity(_, _local_only), do: []
@spec skip?(Activity.t(), User.t()) :: boolean()
def skip?(activity, user) do
[
:self,
- :blocked,
- :muted,
:followers,
:follows,
:non_followers,
:non_follows,
:recently_followed
]
|> Enum.any?(&skip?(&1, activity, user))
end
@spec skip?(atom(), Activity.t(), User.t()) :: boolean()
def skip?(:self, activity, user) do
activity.data["actor"] == user.ap_id
end
- def skip?(:blocked, activity, user) do
- actor = activity.data["actor"]
- User.blocks?(user, %{ap_id: actor})
- end
-
- def skip?(:muted, activity, user) do
- actor = activity.data["actor"]
-
- (User.mutes?(user, %{ap_id: actor}) and User.muted_notifications?(user, %{ap_id: actor})) or
- CommonAPI.thread_muted?(user, activity)
- end
-
def skip?(
:followers,
activity,
%{info: %{notification_settings: %{"followers" => false}}} = user
) do
actor = activity.data["actor"]
follower = User.get_cached_by_ap_id(actor)
User.following?(follower, user)
end
def skip?(
:non_followers,
activity,
%{info: %{notification_settings: %{"non_followers" => false}}} = user
) do
actor = activity.data["actor"]
follower = User.get_cached_by_ap_id(actor)
!User.following?(follower, user)
end
def skip?(:follows, activity, %{info: %{notification_settings: %{"follows" => false}}} = user) do
actor = activity.data["actor"]
followed = User.get_cached_by_ap_id(actor)
User.following?(user, followed)
end
def skip?(
:non_follows,
activity,
%{info: %{notification_settings: %{"non_follows" => false}}} = user
) do
actor = activity.data["actor"]
followed = User.get_cached_by_ap_id(actor)
!User.following?(user, followed)
end
def skip?(:recently_followed, %{data: %{"type" => "Follow"}} = activity, user) do
actor = activity.data["actor"]
Notification.for_user(user)
|> Enum.any?(fn
%{activity: %{data: %{"type" => "Follow", "actor" => ^actor}}} -> true
_ -> false
end)
end
def skip?(_, _, _), do: false
end
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api.ex b/lib/pleroma/web/mastodon_api/mastodon_api.ex
index c82b20123..46944dcbc 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api.ex
@@ -1,88 +1,89 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
import Ecto.Query
import Ecto.Changeset
alias Pleroma.Activity
alias Pleroma.Notification
alias Pleroma.Pagination
alias Pleroma.ScheduledActivity
alias Pleroma.User
alias Pleroma.Web.CommonAPI
def follow(follower, followed, params \\ %{}) do
options = cast_params(params)
reblogs = options[:reblogs]
result =
if not User.following?(follower, followed) do
CommonAPI.follow(follower, followed)
else
{:ok, follower, followed, nil}
end
with {:ok, follower, followed, _} <- result do
reblogs
|> case do
false -> CommonAPI.hide_reblogs(follower, followed)
_ -> CommonAPI.show_reblogs(follower, followed)
end
|> case do
{:ok, follower} -> {:ok, follower}
_ -> {:ok, follower}
end
end
end
def get_followers(user, params \\ %{}) do
user
|> User.get_followers_query()
|> Pagination.fetch_paginated(params)
end
def get_friends(user, params \\ %{}) do
user
|> User.get_friends_query()
|> Pagination.fetch_paginated(params)
end
def get_notifications(user, params \\ %{}) do
options = cast_params(params)
user
- |> Notification.for_user_query()
+ |> Notification.for_user_query(options)
|> restrict(:exclude_types, options)
|> Pagination.fetch_paginated(params)
end
def get_scheduled_activities(user, params \\ %{}) do
user
|> ScheduledActivity.for_user_query()
|> Pagination.fetch_paginated(params)
end
defp cast_params(params) do
param_types = %{
exclude_types: {:array, :string},
- reblogs: :boolean
+ reblogs: :boolean,
+ with_muted: :boolean
}
changeset = cast({%{}, param_types}, params, Map.keys(param_types))
changeset.changes
end
defp restrict(query, :exclude_types, %{exclude_types: mastodon_types = [_ | _]}) do
ap_types =
mastodon_types
|> Enum.map(&Activity.from_mastodon_notification_type/1)
|> Enum.filter(& &1)
query
|> where([q, a], not fragment("? @> ARRAY[?->>'type']::varchar[]", ^ap_types, a.data))
end
defp restrict(query, _, _), do: query
end
diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex
index 45ef7be3d..0313560a8 100644
--- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex
+++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex
@@ -1,798 +1,805 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.TwitterAPI.Controller do
use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
alias Ecto.Changeset
alias Pleroma.Activity
alias Pleroma.Formatter
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.TwitterAPI.ActivityView
alias Pleroma.Web.TwitterAPI.NotificationView
alias Pleroma.Web.TwitterAPI.TokenView
alias Pleroma.Web.TwitterAPI.TwitterAPI
alias Pleroma.Web.TwitterAPI.UserView
require Logger
plug(:only_if_public_instance when action in [:public_timeline, :public_and_external_timeline])
action_fallback(:errors)
def verify_credentials(%{assigns: %{user: user}} = conn, _params) do
token = Phoenix.Token.sign(conn, "user socket", user.id)
conn
|> put_view(UserView)
|> render("show.json", %{user: user, token: token, for: user})
end
def status_update(%{assigns: %{user: user}} = conn, %{"status" => _} = status_data) do
with media_ids <- extract_media_ids(status_data),
{:ok, activity} <-
TwitterAPI.create_status(user, Map.put(status_data, "media_ids", media_ids)) do
conn
|> json(ActivityView.render("activity.json", activity: activity, for: user))
else
_ -> empty_status_reply(conn)
end
end
def status_update(conn, _status_data) do
empty_status_reply(conn)
end
defp empty_status_reply(conn) do
bad_request_reply(conn, "Client must provide a 'status' parameter with a value.")
end
defp extract_media_ids(status_data) do
with media_ids when not is_nil(media_ids) <- status_data["media_ids"],
split_ids <- String.split(media_ids, ","),
clean_ids <- Enum.reject(split_ids, fn id -> String.length(id) == 0 end) do
clean_ids
else
_e -> []
end
end
def public_and_external_timeline(%{assigns: %{user: user}} = conn, params) do
params =
params
|> Map.put("type", ["Create", "Announce"])
|> Map.put("blocking_user", user)
activities = ActivityPub.fetch_public_activities(params)
conn
|> put_view(ActivityView)
|> render("index.json", %{activities: activities, for: user})
end
def public_timeline(%{assigns: %{user: user}} = conn, params) do
params =
params
|> Map.put("type", ["Create", "Announce"])
|> Map.put("local_only", true)
|> Map.put("blocking_user", user)
activities = ActivityPub.fetch_public_activities(params)
conn
|> put_view(ActivityView)
|> render("index.json", %{activities: activities, for: user})
end
def friends_timeline(%{assigns: %{user: user}} = conn, params) do
params =
params
|> Map.put("type", ["Create", "Announce", "Follow", "Like"])
|> Map.put("blocking_user", user)
|> Map.put("user", user)
activities = ActivityPub.fetch_activities([user.ap_id | user.following], params)
conn
|> put_view(ActivityView)
|> render("index.json", %{activities: activities, for: user})
end
def show_user(conn, params) do
for_user = conn.assigns.user
with {:ok, shown} <- TwitterAPI.get_user(params),
true <-
User.auth_active?(shown) ||
(for_user && (for_user.id == shown.id || User.superuser?(for_user))) do
params =
if for_user do
%{user: shown, for: for_user}
else
%{user: shown}
end
conn
|> put_view(UserView)
|> render("show.json", params)
else
{:error, msg} ->
bad_request_reply(conn, msg)
false ->
conn
|> put_status(404)
|> json(%{error: "Unconfirmed user"})
end
end
def user_timeline(%{assigns: %{user: user}} = conn, params) do
case TwitterAPI.get_user(user, params) do
{:ok, target_user} ->
# Twitter and ActivityPub use a different name and sense for this parameter.
{include_rts, params} = Map.pop(params, "include_rts")
params =
case include_rts do
x when x == "false" or x == "0" -> Map.put(params, "exclude_reblogs", "true")
_ -> params
end
activities = ActivityPub.fetch_user_activities(target_user, user, params)
conn
|> put_view(ActivityView)
|> render("index.json", %{activities: activities, for: user})
{:error, msg} ->
bad_request_reply(conn, msg)
end
end
def mentions_timeline(%{assigns: %{user: user}} = conn, params) do
params =
params
|> Map.put("type", ["Create", "Announce", "Follow", "Like"])
|> Map.put("blocking_user", user)
|> Map.put(:visibility, ~w[unlisted public private])
activities = ActivityPub.fetch_activities([user.ap_id], params)
conn
|> put_view(ActivityView)
|> render("index.json", %{activities: activities, for: user})
end
def dm_timeline(%{assigns: %{user: user}} = conn, params) do
params =
params
|> Map.put("type", "Create")
|> Map.put("blocking_user", user)
|> Map.put("user", user)
|> Map.put(:visibility, "direct")
|> Map.put(:order, :desc)
activities =
ActivityPub.fetch_activities_query([user.ap_id], params)
|> Repo.all()
conn
|> put_view(ActivityView)
|> render("index.json", %{activities: activities, for: user})
end
def notifications(%{assigns: %{user: user}} = conn, params) do
+ params =
+ if Map.has_key?(params, "with_muted") do
+ Map.put(params, :with_muted, params["with_muted"] in [true, "True", "true", "1"])
+ else
+ params
+ end
+
notifications = Notification.for_user(user, params)
conn
|> put_view(NotificationView)
|> render("notification.json", %{notifications: notifications, for: user})
end
def notifications_read(%{assigns: %{user: user}} = conn, %{"latest_id" => latest_id} = params) do
Notification.set_read_up_to(user, latest_id)
notifications = Notification.for_user(user, params)
conn
|> put_view(NotificationView)
|> render("notification.json", %{notifications: notifications, for: user})
end
def notifications_read(%{assigns: %{user: _user}} = conn, _) do
bad_request_reply(conn, "You need to specify latest_id")
end
def follow(%{assigns: %{user: user}} = conn, params) do
case TwitterAPI.follow(user, params) do
{:ok, user, followed, _activity} ->
conn
|> put_view(UserView)
|> render("show.json", %{user: followed, for: user})
{:error, msg} ->
forbidden_json_reply(conn, msg)
end
end
def block(%{assigns: %{user: user}} = conn, params) do
case TwitterAPI.block(user, params) do
{:ok, user, blocked} ->
conn
|> put_view(UserView)
|> render("show.json", %{user: blocked, for: user})
{:error, msg} ->
forbidden_json_reply(conn, msg)
end
end
def unblock(%{assigns: %{user: user}} = conn, params) do
case TwitterAPI.unblock(user, params) do
{:ok, user, blocked} ->
conn
|> put_view(UserView)
|> render("show.json", %{user: blocked, for: user})
{:error, msg} ->
forbidden_json_reply(conn, msg)
end
end
def delete_post(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with {:ok, activity} <- TwitterAPI.delete(user, id) do
conn
|> put_view(ActivityView)
|> render("activity.json", %{activity: activity, for: user})
end
end
def unfollow(%{assigns: %{user: user}} = conn, params) do
case TwitterAPI.unfollow(user, params) do
{:ok, user, unfollowed} ->
conn
|> put_view(UserView)
|> render("show.json", %{user: unfollowed, for: user})
{:error, msg} ->
forbidden_json_reply(conn, msg)
end
end
def fetch_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with %Activity{} = activity <- Activity.get_by_id(id),
true <- Visibility.visible_for_user?(activity, user) do
conn
|> put_view(ActivityView)
|> render("activity.json", %{activity: activity, for: user})
end
end
def fetch_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with context when is_binary(context) <- Utils.conversation_id_to_context(id),
activities <-
ActivityPub.fetch_activities_for_context(context, %{
"blocking_user" => user,
"user" => user
}) do
conn
|> put_view(ActivityView)
|> render("index.json", %{activities: activities, for: user})
end
end
@doc """
Updates metadata of uploaded media object.
Derived from [Twitter API endpoint](https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-metadata-create).
"""
def update_media(%{assigns: %{user: user}} = conn, %{"media_id" => id} = data) do
object = Repo.get(Object, id)
description = get_in(data, ["alt_text", "text"]) || data["name"] || data["description"]
{conn, status, response_body} =
cond do
!object ->
{halt(conn), :not_found, ""}
!Object.authorize_mutation(object, user) ->
{halt(conn), :forbidden, "You can only update your own uploads."}
!is_binary(description) ->
{conn, :not_modified, ""}
true ->
new_data = Map.put(object.data, "name", description)
{:ok, _} =
object
|> Object.change(%{data: new_data})
|> Repo.update()
{conn, :no_content, ""}
end
conn
|> put_status(status)
|> json(response_body)
end
def upload(%{assigns: %{user: user}} = conn, %{"media" => media}) do
response = TwitterAPI.upload(media, user)
conn
|> put_resp_content_type("application/atom+xml")
|> send_resp(200, response)
end
def upload_json(%{assigns: %{user: user}} = conn, %{"media" => media}) do
response = TwitterAPI.upload(media, user, "json")
conn
|> json_reply(200, response)
end
def get_by_id_or_ap_id(id) do
activity = Activity.get_by_id(id) || Activity.get_create_by_object_ap_id(id)
if activity.data["type"] == "Create" do
activity
else
Activity.get_create_by_object_ap_id(activity.data["object"])
end
end
def favorite(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with {:ok, activity} <- TwitterAPI.fav(user, id) do
conn
|> put_view(ActivityView)
|> render("activity.json", %{activity: activity, for: user})
else
_ -> json_reply(conn, 400, Jason.encode!(%{}))
end
end
def unfavorite(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with {:ok, activity} <- TwitterAPI.unfav(user, id) do
conn
|> put_view(ActivityView)
|> render("activity.json", %{activity: activity, for: user})
else
_ -> json_reply(conn, 400, Jason.encode!(%{}))
end
end
def retweet(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with {:ok, activity} <- TwitterAPI.repeat(user, id) do
conn
|> put_view(ActivityView)
|> render("activity.json", %{activity: activity, for: user})
else
_ -> json_reply(conn, 400, Jason.encode!(%{}))
end
end
def unretweet(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with {:ok, activity} <- TwitterAPI.unrepeat(user, id) do
conn
|> put_view(ActivityView)
|> render("activity.json", %{activity: activity, for: user})
else
_ -> json_reply(conn, 400, Jason.encode!(%{}))
end
end
def pin(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with {:ok, activity} <- TwitterAPI.pin(user, id) do
conn
|> put_view(ActivityView)
|> render("activity.json", %{activity: activity, for: user})
else
{:error, message} -> bad_request_reply(conn, message)
err -> err
end
end
def unpin(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with {:ok, activity} <- TwitterAPI.unpin(user, id) do
conn
|> put_view(ActivityView)
|> render("activity.json", %{activity: activity, for: user})
else
{:error, message} -> bad_request_reply(conn, message)
err -> err
end
end
def register(conn, params) do
with {:ok, user} <- TwitterAPI.register_user(params) do
conn
|> put_view(UserView)
|> render("show.json", %{user: user})
else
{:error, errors} ->
conn
|> json_reply(400, Jason.encode!(errors))
end
end
def password_reset(conn, params) do
nickname_or_email = params["email"] || params["nickname"]
with {:ok, _} <- TwitterAPI.password_reset(nickname_or_email) do
json_response(conn, :no_content, "")
end
end
def confirm_email(conn, %{"user_id" => uid, "token" => token}) do
with %User{} = user <- User.get_cached_by_id(uid),
true <- user.local,
true <- user.info.confirmation_pending,
true <- user.info.confirmation_token == token,
info_change <- User.Info.confirmation_changeset(user.info, need_confirmation: false),
changeset <- Changeset.change(user) |> Changeset.put_embed(:info, info_change),
{:ok, _} <- User.update_and_set_cache(changeset) do
conn
|> redirect(to: "/")
end
end
def resend_confirmation_email(conn, params) do
nickname_or_email = params["email"] || params["nickname"]
with %User{} = user <- User.get_by_nickname_or_email(nickname_or_email),
{:ok, _} <- User.try_send_confirmation_email(user) do
conn
|> json_response(:no_content, "")
end
end
def update_avatar(%{assigns: %{user: user}} = conn, %{"img" => ""}) do
change = Changeset.change(user, %{avatar: nil})
{:ok, user} = User.update_and_set_cache(change)
CommonAPI.update(user)
conn
|> put_view(UserView)
|> render("show.json", %{user: user, for: user})
end
def update_avatar(%{assigns: %{user: user}} = conn, params) do
{:ok, object} = ActivityPub.upload(params, type: :avatar)
change = Changeset.change(user, %{avatar: object.data})
{:ok, user} = User.update_and_set_cache(change)
CommonAPI.update(user)
conn
|> put_view(UserView)
|> render("show.json", %{user: user, for: user})
end
def update_banner(%{assigns: %{user: user}} = conn, %{"banner" => ""}) do
with new_info <- %{"banner" => %{}},
info_cng <- User.Info.profile_update(user.info, new_info),
changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
{:ok, user} <- User.update_and_set_cache(changeset) do
CommonAPI.update(user)
response = %{url: nil} |> Jason.encode!()
conn
|> json_reply(200, response)
end
end
def update_banner(%{assigns: %{user: user}} = conn, params) do
with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}, type: :banner),
new_info <- %{"banner" => object.data},
info_cng <- User.Info.profile_update(user.info, new_info),
changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
{:ok, user} <- User.update_and_set_cache(changeset) do
CommonAPI.update(user)
%{"url" => [%{"href" => href} | _]} = object.data
response = %{url: href} |> Jason.encode!()
conn
|> json_reply(200, response)
end
end
def update_background(%{assigns: %{user: user}} = conn, %{"img" => ""}) do
with new_info <- %{"background" => %{}},
info_cng <- User.Info.profile_update(user.info, new_info),
changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
{:ok, _user} <- User.update_and_set_cache(changeset) do
response = %{url: nil} |> Jason.encode!()
conn
|> json_reply(200, response)
end
end
def update_background(%{assigns: %{user: user}} = conn, params) do
with {:ok, object} <- ActivityPub.upload(params, type: :background),
new_info <- %{"background" => object.data},
info_cng <- User.Info.profile_update(user.info, new_info),
changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
{:ok, _user} <- User.update_and_set_cache(changeset) do
%{"url" => [%{"href" => href} | _]} = object.data
response = %{url: href} |> Jason.encode!()
conn
|> json_reply(200, response)
end
end
def external_profile(%{assigns: %{user: current_user}} = conn, %{"profileurl" => uri}) do
with {:ok, user_map} <- TwitterAPI.get_external_profile(current_user, uri),
response <- Jason.encode!(user_map) do
conn
|> json_reply(200, response)
else
_e ->
conn
|> put_status(404)
|> json(%{error: "Can't find user"})
end
end
def followers(%{assigns: %{user: for_user}} = conn, params) do
{:ok, page} = Ecto.Type.cast(:integer, params["page"] || 1)
with {:ok, user} <- TwitterAPI.get_user(for_user, params),
{:ok, followers} <- User.get_followers(user, page) do
followers =
cond do
for_user && user.id == for_user.id -> followers
user.info.hide_followers -> []
true -> followers
end
conn
|> put_view(UserView)
|> render("index.json", %{users: followers, for: conn.assigns[:user]})
else
_e -> bad_request_reply(conn, "Can't get followers")
end
end
def friends(%{assigns: %{user: for_user}} = conn, params) do
{:ok, page} = Ecto.Type.cast(:integer, params["page"] || 1)
{:ok, export} = Ecto.Type.cast(:boolean, params["all"] || false)
page = if export, do: nil, else: page
with {:ok, user} <- TwitterAPI.get_user(conn.assigns[:user], params),
{:ok, friends} <- User.get_friends(user, page) do
friends =
cond do
for_user && user.id == for_user.id -> friends
user.info.hide_follows -> []
true -> friends
end
conn
|> put_view(UserView)
|> render("index.json", %{users: friends, for: conn.assigns[:user]})
else
_e -> bad_request_reply(conn, "Can't get friends")
end
end
def oauth_tokens(%{assigns: %{user: user}} = conn, _params) do
with oauth_tokens <- Token.get_user_tokens(user) do
conn
|> put_view(TokenView)
|> render("index.json", %{tokens: oauth_tokens})
end
end
def revoke_token(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
Token.delete_user_token(user, id)
json_reply(conn, 201, "")
end
def blocks(%{assigns: %{user: user}} = conn, _params) do
with blocked_users <- User.blocked_users(user) do
conn
|> put_view(UserView)
|> render("index.json", %{users: blocked_users, for: user})
end
end
def friend_requests(conn, params) do
with {:ok, user} <- TwitterAPI.get_user(conn.assigns[:user], params),
{:ok, friend_requests} <- User.get_follow_requests(user) do
conn
|> put_view(UserView)
|> render("index.json", %{users: friend_requests, for: conn.assigns[:user]})
else
_e -> bad_request_reply(conn, "Can't get friend requests")
end
end
def approve_friend_request(conn, %{"user_id" => uid} = _params) do
with followed <- conn.assigns[:user],
%User{} = follower <- User.get_cached_by_id(uid),
{:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do
conn
|> put_view(UserView)
|> render("show.json", %{user: follower, for: followed})
else
e -> bad_request_reply(conn, "Can't approve user: #{inspect(e)}")
end
end
def deny_friend_request(conn, %{"user_id" => uid} = _params) do
with followed <- conn.assigns[:user],
%User{} = follower <- User.get_cached_by_id(uid),
{:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do
conn
|> put_view(UserView)
|> render("show.json", %{user: follower, for: followed})
else
e -> bad_request_reply(conn, "Can't deny user: #{inspect(e)}")
end
end
def friends_ids(%{assigns: %{user: user}} = conn, _params) do
with {:ok, friends} <- User.get_friends(user) do
ids =
friends
|> Enum.map(fn x -> x.id end)
|> Jason.encode!()
json(conn, ids)
else
_e -> bad_request_reply(conn, "Can't get friends")
end
end
def empty_array(conn, _params) do
json(conn, Jason.encode!([]))
end
def raw_empty_array(conn, _params) do
json(conn, [])
end
defp build_info_cng(user, params) do
info_params =
[
"no_rich_text",
"locked",
"hide_followers",
"hide_follows",
"hide_favorites",
"show_role",
"skip_thread_containment"
]
|> Enum.reduce(%{}, fn key, res ->
if value = params[key] do
Map.put(res, key, value == "true")
else
res
end
end)
info_params =
if value = params["default_scope"] do
Map.put(info_params, "default_scope", value)
else
info_params
end
User.Info.profile_update(user.info, info_params)
end
defp parse_profile_bio(user, params) do
if bio = params["description"] do
emojis_text = (params["description"] || "") <> " " <> (params["name"] || "")
emojis =
((user.info.emoji || []) ++ Formatter.get_emoji_map(emojis_text))
|> Enum.dedup()
user_info =
user.info
|> Map.put(
"emoji",
emojis
)
params
|> Map.put("bio", User.parse_bio(bio, user))
|> Map.put("info", user_info)
else
params
end
end
def update_profile(%{assigns: %{user: user}} = conn, params) do
params = parse_profile_bio(user, params)
info_cng = build_info_cng(user, params)
with changeset <- User.update_changeset(user, params),
changeset <- Ecto.Changeset.put_embed(changeset, :info, info_cng),
{:ok, user} <- User.update_and_set_cache(changeset) do
CommonAPI.update(user)
conn
|> put_view(UserView)
|> render("user.json", %{user: user, for: user})
else
error ->
Logger.debug("Can't update user: #{inspect(error)}")
bad_request_reply(conn, "Can't update user")
end
end
def search(%{assigns: %{user: user}} = conn, %{"q" => _query} = params) do
activities = TwitterAPI.search(user, params)
conn
|> put_view(ActivityView)
|> render("index.json", %{activities: activities, for: user})
end
def search_user(%{assigns: %{user: user}} = conn, %{"query" => query}) do
users = User.search(query, resolve: true, for_user: user)
conn
|> put_view(UserView)
|> render("index.json", %{users: users, for: user})
end
defp bad_request_reply(conn, error_message) do
json = error_json(conn, error_message)
json_reply(conn, 400, json)
end
defp json_reply(conn, status, json) do
conn
|> put_resp_content_type("application/json")
|> send_resp(status, json)
end
defp forbidden_json_reply(conn, error_message) do
json = error_json(conn, error_message)
json_reply(conn, 403, json)
end
def only_if_public_instance(%{assigns: %{user: %User{}}} = conn, _), do: conn
def only_if_public_instance(conn, _) do
if Pleroma.Config.get([:instance, :public]) do
conn
else
conn
|> forbidden_json_reply("Invalid credentials.")
|> halt()
end
end
defp error_json(conn, error_message) do
%{"error" => error_message, "request" => conn.request_path} |> Jason.encode!()
end
def errors(conn, {:param_cast, _}) do
conn
|> put_status(400)
|> json("Invalid parameters")
end
def errors(conn, _) do
conn
|> put_status(500)
|> json("Something went wrong")
end
end
diff --git a/test/notification_test.exs b/test/notification_test.exs
index 759d98c26..dda570b49 100644
--- a/test/notification_test.exs
+++ b/test/notification_test.exs
@@ -1,546 +1,640 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.NotificationTest do
use Pleroma.DataCase
alias Pleroma.Notification
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.Streamer
alias Pleroma.Web.TwitterAPI.TwitterAPI
import Pleroma.Factory
describe "create_notifications" do
test "notifies someone when they are directly addressed" do
user = insert(:user)
other_user = insert(:user)
third_user = insert(:user)
{:ok, activity} =
TwitterAPI.create_status(user, %{
"status" => "hey @#{other_user.nickname} and @#{third_user.nickname}"
})
{:ok, [notification, other_notification]} = Notification.create_notifications(activity)
notified_ids = Enum.sort([notification.user_id, other_notification.user_id])
assert notified_ids == [other_user.id, third_user.id]
assert notification.activity_id == activity.id
assert other_notification.activity_id == activity.id
end
test "it creates a notification for subscribed users" do
user = insert(:user)
subscriber = insert(:user)
User.subscribe(subscriber, user)
{:ok, status} = TwitterAPI.create_status(user, %{"status" => "Akariiiin"})
{:ok, [notification]} = Notification.create_notifications(status)
assert notification.user_id == subscriber.id
end
end
describe "create_notification" do
setup do
GenServer.start(Streamer, %{}, name: Streamer)
on_exit(fn ->
if pid = Process.whereis(Streamer) do
Process.exit(pid, :kill)
end
end)
end
test "it creates a notification for user and send to the 'user' and the 'user:notification' stream" do
user = insert(:user)
task = Task.async(fn -> assert_receive {:text, _}, 4_000 end)
task_user_notification = Task.async(fn -> assert_receive {:text, _}, 4_000 end)
Streamer.add_socket("user", %{transport_pid: task.pid, assigns: %{user: user}})
Streamer.add_socket(
"user:notification",
%{transport_pid: task_user_notification.pid, assigns: %{user: user}}
)
activity = insert(:note_activity)
notify = Notification.create_notification(activity, user)
assert notify.user_id == user.id
Task.await(task)
Task.await(task_user_notification)
end
- test "it doesn't create a notification for user if the user blocks the activity author" do
+ test "it creates a notification for user if the user blocks the activity author" do
activity = insert(:note_activity)
author = User.get_cached_by_ap_id(activity.data["actor"])
user = insert(:user)
{:ok, user} = User.block(user, author)
- refute Notification.create_notification(activity, user)
+ assert Notification.create_notification(activity, user)
end
- test "it doesn't create a notificatin for the user if the user mutes the activity author" do
+ test "it creates a notificatin for the user if the user mutes the activity author" do
muter = insert(:user)
muted = insert(:user)
{:ok, _} = User.mute(muter, muted)
muter = Repo.get(User, muter.id)
{:ok, activity} = CommonAPI.post(muted, %{"status" => "Hi @#{muter.nickname}"})
- refute Notification.create_notification(activity, muter)
+ assert Notification.create_notification(activity, muter)
end
test "notification created if user is muted without notifications" do
muter = insert(:user)
muted = insert(:user)
{:ok, muter} = User.mute(muter, muted, false)
{:ok, activity} = CommonAPI.post(muted, %{"status" => "Hi @#{muter.nickname}"})
assert Notification.create_notification(activity, muter)
end
- test "it doesn't create a notification for an activity from a muted thread" do
+ test "it creates a notification for an activity from a muted thread" do
muter = insert(:user)
other_user = insert(:user)
{:ok, activity} = CommonAPI.post(muter, %{"status" => "hey"})
CommonAPI.add_mute(muter, activity)
{:ok, activity} =
CommonAPI.post(other_user, %{
"status" => "Hi @#{muter.nickname}",
"in_reply_to_status_id" => activity.id
})
- refute Notification.create_notification(activity, muter)
+ assert Notification.create_notification(activity, muter)
end
test "it disables notifications from followers" do
follower = insert(:user)
followed = insert(:user, info: %{notification_settings: %{"followers" => false}})
User.follow(follower, followed)
{:ok, activity} = CommonAPI.post(follower, %{"status" => "hey @#{followed.nickname}"})
refute Notification.create_notification(activity, followed)
end
test "it disables notifications from non-followers" do
follower = insert(:user)
followed = insert(:user, info: %{notification_settings: %{"non_followers" => false}})
{:ok, activity} = CommonAPI.post(follower, %{"status" => "hey @#{followed.nickname}"})
refute Notification.create_notification(activity, followed)
end
test "it disables notifications from people the user follows" do
follower = insert(:user, info: %{notification_settings: %{"follows" => false}})
followed = insert(:user)
User.follow(follower, followed)
follower = Repo.get(User, follower.id)
{:ok, activity} = CommonAPI.post(followed, %{"status" => "hey @#{follower.nickname}"})
refute Notification.create_notification(activity, follower)
end
test "it disables notifications from people the user does not follow" do
follower = insert(:user, info: %{notification_settings: %{"non_follows" => false}})
followed = insert(:user)
{:ok, activity} = CommonAPI.post(followed, %{"status" => "hey @#{follower.nickname}"})
refute Notification.create_notification(activity, follower)
end
test "it doesn't create a notification for user if he is the activity author" do
activity = insert(:note_activity)
author = User.get_cached_by_ap_id(activity.data["actor"])
refute Notification.create_notification(activity, author)
end
test "it doesn't create a notification for follow-unfollow-follow chains" do
user = insert(:user)
followed_user = insert(:user)
{:ok, _, _, activity} = TwitterAPI.follow(user, %{"user_id" => followed_user.id})
Notification.create_notification(activity, followed_user)
TwitterAPI.unfollow(user, %{"user_id" => followed_user.id})
{:ok, _, _, activity_dupe} = TwitterAPI.follow(user, %{"user_id" => followed_user.id})
refute Notification.create_notification(activity_dupe, followed_user)
end
test "it doesn't create a notification for like-unlike-like chains" do
user = insert(:user)
liked_user = insert(:user)
{:ok, status} = TwitterAPI.create_status(liked_user, %{"status" => "Yui is best yuru"})
{:ok, fav_status} = TwitterAPI.fav(user, status.id)
Notification.create_notification(fav_status, liked_user)
TwitterAPI.unfav(user, status.id)
{:ok, dupe} = TwitterAPI.fav(user, status.id)
refute Notification.create_notification(dupe, liked_user)
end
test "it doesn't create a notification for repeat-unrepeat-repeat chains" do
user = insert(:user)
retweeted_user = insert(:user)
{:ok, status} =
TwitterAPI.create_status(retweeted_user, %{
"status" => "Send dupe notifications to the shadow realm"
})
{:ok, retweeted_activity} = TwitterAPI.repeat(user, status.id)
Notification.create_notification(retweeted_activity, retweeted_user)
TwitterAPI.unrepeat(user, status.id)
{:ok, dupe} = TwitterAPI.repeat(user, status.id)
refute Notification.create_notification(dupe, retweeted_user)
end
test "it doesn't create duplicate notifications for follow+subscribed users" do
user = insert(:user)
subscriber = insert(:user)
{:ok, _, _, _} = TwitterAPI.follow(subscriber, %{"user_id" => user.id})
User.subscribe(subscriber, user)
{:ok, status} = TwitterAPI.create_status(user, %{"status" => "Akariiiin"})
{:ok, [_notif]} = Notification.create_notifications(status)
end
test "it doesn't create subscription notifications if the recipient cannot see the status" do
user = insert(:user)
subscriber = insert(:user)
User.subscribe(subscriber, user)
{:ok, status} =
TwitterAPI.create_status(user, %{"status" => "inwisible", "visibility" => "direct"})
assert {:ok, []} == Notification.create_notifications(status)
end
end
describe "get notification" do
test "it gets a notification that belongs to the user" do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} =
TwitterAPI.create_status(user, %{"status" => "hey @#{other_user.nickname}"})
{:ok, [notification]} = Notification.create_notifications(activity)
{:ok, notification} = Notification.get(other_user, notification.id)
assert notification.user_id == other_user.id
end
test "it returns error if the notification doesn't belong to the user" do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} =
TwitterAPI.create_status(user, %{"status" => "hey @#{other_user.nickname}"})
{:ok, [notification]} = Notification.create_notifications(activity)
{:error, _notification} = Notification.get(user, notification.id)
end
end
describe "dismiss notification" do
test "it dismisses a notification that belongs to the user" do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} =
TwitterAPI.create_status(user, %{"status" => "hey @#{other_user.nickname}"})
{:ok, [notification]} = Notification.create_notifications(activity)
{:ok, notification} = Notification.dismiss(other_user, notification.id)
assert notification.user_id == other_user.id
end
test "it returns error if the notification doesn't belong to the user" do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} =
TwitterAPI.create_status(user, %{"status" => "hey @#{other_user.nickname}"})
{:ok, [notification]} = Notification.create_notifications(activity)
{:error, _notification} = Notification.dismiss(user, notification.id)
end
end
describe "clear notification" do
test "it clears all notifications belonging to the user" do
user = insert(:user)
other_user = insert(:user)
third_user = insert(:user)
{:ok, activity} =
TwitterAPI.create_status(user, %{
"status" => "hey @#{other_user.nickname} and @#{third_user.nickname} !"
})
{:ok, _notifs} = Notification.create_notifications(activity)
{:ok, activity} =
TwitterAPI.create_status(user, %{
"status" => "hey again @#{other_user.nickname} and @#{third_user.nickname} !"
})
{:ok, _notifs} = Notification.create_notifications(activity)
Notification.clear(other_user)
assert Notification.for_user(other_user) == []
assert Notification.for_user(third_user) != []
end
end
describe "set_read_up_to()" do
test "it sets all notifications as read up to a specified notification ID" do
user = insert(:user)
other_user = insert(:user)
{:ok, _activity} =
TwitterAPI.create_status(user, %{
"status" => "hey @#{other_user.nickname}!"
})
{:ok, _activity} =
TwitterAPI.create_status(user, %{
"status" => "hey again @#{other_user.nickname}!"
})
[n2, n1] = notifs = Notification.for_user(other_user)
assert length(notifs) == 2
assert n2.id > n1.id
{:ok, _activity} =
TwitterAPI.create_status(user, %{
"status" => "hey yet again @#{other_user.nickname}!"
})
Notification.set_read_up_to(other_user, n2.id)
[n3, n2, n1] = Notification.for_user(other_user)
assert n1.seen == true
assert n2.seen == true
assert n3.seen == false
end
end
describe "notification target determination" do
test "it sends notifications to addressed users in new messages" do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} =
CommonAPI.post(user, %{
"status" => "hey @#{other_user.nickname}!"
})
assert other_user in Notification.get_notified_from_activity(activity)
end
test "it sends notifications to mentioned users in new messages" do
user = insert(:user)
other_user = insert(:user)
create_activity = %{
"@context" => "https://www.w3.org/ns/activitystreams",
"type" => "Create",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"actor" => user.ap_id,
"object" => %{
"type" => "Note",
"content" => "message with a Mention tag, but no explicit tagging",
"tag" => [
%{
"type" => "Mention",
"href" => other_user.ap_id,
"name" => other_user.nickname
}
],
"attributedTo" => user.ap_id
}
}
{:ok, activity} = Transmogrifier.handle_incoming(create_activity)
assert other_user in Notification.get_notified_from_activity(activity)
end
test "it does not send notifications to users who are only cc in new messages" do
user = insert(:user)
other_user = insert(:user)
create_activity = %{
"@context" => "https://www.w3.org/ns/activitystreams",
"type" => "Create",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [other_user.ap_id],
"actor" => user.ap_id,
"object" => %{
"type" => "Note",
"content" => "hi everyone",
"attributedTo" => user.ap_id
}
}
{:ok, activity} = Transmogrifier.handle_incoming(create_activity)
assert other_user not in Notification.get_notified_from_activity(activity)
end
test "it does not send notification to mentioned users in likes" do
user = insert(:user)
other_user = insert(:user)
third_user = insert(:user)
{:ok, activity_one} =
CommonAPI.post(user, %{
"status" => "hey @#{other_user.nickname}!"
})
{:ok, activity_two, _} = CommonAPI.favorite(activity_one.id, third_user)
assert other_user not in Notification.get_notified_from_activity(activity_two)
end
test "it does not send notification to mentioned users in announces" do
user = insert(:user)
other_user = insert(:user)
third_user = insert(:user)
{:ok, activity_one} =
CommonAPI.post(user, %{
"status" => "hey @#{other_user.nickname}!"
})
{:ok, activity_two, _} = CommonAPI.repeat(activity_one.id, third_user)
assert other_user not in Notification.get_notified_from_activity(activity_two)
end
end
describe "notification lifecycle" do
test "liking an activity results in 1 notification, then 0 if the activity is deleted" do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "test post"})
assert Enum.empty?(Notification.for_user(user))
{:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
assert length(Notification.for_user(user)) == 1
{:ok, _} = CommonAPI.delete(activity.id, user)
assert Enum.empty?(Notification.for_user(user))
end
test "liking an activity results in 1 notification, then 0 if the activity is unliked" do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "test post"})
assert Enum.empty?(Notification.for_user(user))
{:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
assert length(Notification.for_user(user)) == 1
{:ok, _, _, _} = CommonAPI.unfavorite(activity.id, other_user)
assert Enum.empty?(Notification.for_user(user))
end
test "repeating an activity results in 1 notification, then 0 if the activity is deleted" do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "test post"})
assert Enum.empty?(Notification.for_user(user))
{:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
assert length(Notification.for_user(user)) == 1
{:ok, _} = CommonAPI.delete(activity.id, user)
assert Enum.empty?(Notification.for_user(user))
end
test "repeating an activity results in 1 notification, then 0 if the activity is unrepeated" do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "test post"})
assert Enum.empty?(Notification.for_user(user))
{:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
assert length(Notification.for_user(user)) == 1
{:ok, _, _} = CommonAPI.unrepeat(activity.id, other_user)
assert Enum.empty?(Notification.for_user(user))
end
test "liking an activity which is already deleted does not generate a notification" do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "test post"})
assert Enum.empty?(Notification.for_user(user))
{:ok, _deletion_activity} = CommonAPI.delete(activity.id, user)
assert Enum.empty?(Notification.for_user(user))
{:error, _} = CommonAPI.favorite(activity.id, other_user)
assert Enum.empty?(Notification.for_user(user))
end
test "repeating an activity which is already deleted does not generate a notification" do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "test post"})
assert Enum.empty?(Notification.for_user(user))
{:ok, _deletion_activity} = CommonAPI.delete(activity.id, user)
assert Enum.empty?(Notification.for_user(user))
{:error, _} = CommonAPI.repeat(activity.id, other_user)
assert Enum.empty?(Notification.for_user(user))
end
test "replying to a deleted post without tagging does not generate a notification" do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "test post"})
{:ok, _deletion_activity} = CommonAPI.delete(activity.id, user)
{:ok, _reply_activity} =
CommonAPI.post(other_user, %{
"status" => "test reply",
"in_reply_to_status_id" => activity.id
})
assert Enum.empty?(Notification.for_user(user))
end
end
+
+ describe "for_user" do
+ test "it returns notifications for muted user without notifications" do
+ user = insert(:user)
+ muted = insert(:user)
+ {:ok, user} = User.mute(user, muted, false)
+
+ {:ok, _activity} = TwitterAPI.create_status(muted, %{"status" => "hey @#{user.nickname}"})
+
+ assert length(Notification.for_user(user)) == 1
+ end
+
+ test "it doesn't return notifications for muted user with notifications" do
+ user = insert(:user)
+ muted = insert(:user)
+ {:ok, user} = User.mute(user, muted)
+
+ {:ok, _activity} = TwitterAPI.create_status(muted, %{"status" => "hey @#{user.nickname}"})
+
+ assert Notification.for_user(user) == []
+ end
+
+ test "it doesn't return notifications for blocked user" do
+ user = insert(:user)
+ blocked = insert(:user)
+ {:ok, user} = User.block(user, blocked)
+
+ {:ok, _activity} = TwitterAPI.create_status(blocked, %{"status" => "hey @#{user.nickname}"})
+
+ assert Notification.for_user(user) == []
+ end
+
+ test "it doesn't return notificatitons for blocked domain" do
+ user = insert(:user)
+ blocked = insert(:user, ap_id: "http://some-domain.com")
+ {:ok, user} = User.block_domain(user, "some-domain.com")
+
+ {:ok, _activity} = TwitterAPI.create_status(blocked, %{"status" => "hey @#{user.nickname}"})
+
+ assert Notification.for_user(user) == []
+ end
+
+ test "it doesn't return notifications for muted thread" do
+ user = insert(:user)
+ another_user = insert(:user)
+
+ {:ok, activity} =
+ TwitterAPI.create_status(another_user, %{"status" => "hey @#{user.nickname}"})
+
+ {:ok, _} = Pleroma.ThreadMute.add_mute(user.id, activity.data["context"])
+ assert Notification.for_user(user) == []
+ end
+
+ test "it returns notifications for muted user with notifications and with_muted parameter" do
+ user = insert(:user)
+ muted = insert(:user)
+ {:ok, user} = User.mute(user, muted)
+
+ {:ok, _activity} = TwitterAPI.create_status(muted, %{"status" => "hey @#{user.nickname}"})
+
+ assert length(Notification.for_user(user, %{with_muted: true})) == 1
+ end
+
+ test "it returns notifications for blocked user and with_muted parameter" do
+ user = insert(:user)
+ blocked = insert(:user)
+ {:ok, user} = User.block(user, blocked)
+
+ {:ok, _activity} = TwitterAPI.create_status(blocked, %{"status" => "hey @#{user.nickname}"})
+
+ assert length(Notification.for_user(user, %{with_muted: true})) == 1
+ end
+
+ test "it returns notificatitons for blocked domain and with_muted parameter" do
+ user = insert(:user)
+ blocked = insert(:user, ap_id: "http://some-domain.com")
+ {:ok, user} = User.block_domain(user, "some-domain.com")
+
+ {:ok, _activity} = TwitterAPI.create_status(blocked, %{"status" => "hey @#{user.nickname}"})
+
+ assert length(Notification.for_user(user, %{with_muted: true})) == 1
+ end
+
+ test "it returns notifications for muted thread with_muted parameter" do
+ user = insert(:user)
+ another_user = insert(:user)
+
+ {:ok, activity} =
+ TwitterAPI.create_status(another_user, %{"status" => "hey @#{user.nickname}"})
+
+ {:ok, _} = Pleroma.ThreadMute.add_mute(user.id, activity.data["context"])
+ assert length(Notification.for_user(user, %{with_muted: true})) == 1
+ end
+ end
end
diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs
index 01d109bb4..6ffa64dc8 100644
--- a/test/web/mastodon_api/mastodon_api_controller_test.exs
+++ b/test/web/mastodon_api/mastodon_api_controller_test.exs
@@ -1,3584 +1,3607 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
use Pleroma.Web.ConnCase
alias Ecto.Changeset
alias Pleroma.Activity
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.ScheduledActivity
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.FilterView
alias Pleroma.Web.OAuth.App
alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.OStatus
alias Pleroma.Web.Push
alias Pleroma.Web.TwitterAPI.TwitterAPI
import Pleroma.Factory
import ExUnit.CaptureLog
import Tesla.Mock
@image "data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7"
setup do
mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
:ok
end
test "the home timeline", %{conn: conn} do
user = insert(:user)
following = insert(:user)
{:ok, _activity} = TwitterAPI.create_status(following, %{"status" => "test"})
conn =
conn
|> assign(:user, user)
|> get("/api/v1/timelines/home")
assert Enum.empty?(json_response(conn, 200))
{:ok, user} = User.follow(user, following)
conn =
build_conn()
|> assign(:user, user)
|> get("/api/v1/timelines/home")
assert [%{"content" => "test"}] = json_response(conn, 200)
end
test "the public timeline", %{conn: conn} do
following = insert(:user)
capture_log(fn ->
{:ok, _activity} = TwitterAPI.create_status(following, %{"status" => "test"})
{:ok, [_activity]} =
OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873")
conn =
conn
|> get("/api/v1/timelines/public", %{"local" => "False"})
assert length(json_response(conn, 200)) == 2
conn =
build_conn()
|> get("/api/v1/timelines/public", %{"local" => "True"})
assert [%{"content" => "test"}] = json_response(conn, 200)
conn =
build_conn()
|> get("/api/v1/timelines/public", %{"local" => "1"})
assert [%{"content" => "test"}] = json_response(conn, 200)
end)
end
test "the public timeline when public is set to false", %{conn: conn} do
public = Pleroma.Config.get([:instance, :public])
Pleroma.Config.put([:instance, :public], false)
on_exit(fn ->
Pleroma.Config.put([:instance, :public], public)
end)
assert conn
|> get("/api/v1/timelines/public", %{"local" => "False"})
|> json_response(403) == %{"error" => "This resource requires authentication."}
end
describe "posting statuses" do
setup do
user = insert(:user)
conn =
build_conn()
|> assign(:user, user)
[conn: conn]
end
test "posting a status", %{conn: conn} do
idempotency_key = "Pikachu rocks!"
conn_one =
conn
|> put_req_header("idempotency-key", idempotency_key)
|> post("/api/v1/statuses", %{
"status" => "cofe",
"spoiler_text" => "2hu",
"sensitive" => "false"
})
{:ok, ttl} = Cachex.ttl(:idempotency_cache, idempotency_key)
# Six hours
assert ttl > :timer.seconds(6 * 60 * 60 - 1)
assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} =
json_response(conn_one, 200)
assert Activity.get_by_id(id)
conn_two =
conn
|> put_req_header("idempotency-key", idempotency_key)
|> post("/api/v1/statuses", %{
"status" => "cofe",
"spoiler_text" => "2hu",
"sensitive" => "false"
})
assert %{"id" => second_id} = json_response(conn_two, 200)
assert id == second_id
conn_three =
conn
|> post("/api/v1/statuses", %{
"status" => "cofe",
"spoiler_text" => "2hu",
"sensitive" => "false"
})
assert %{"id" => third_id} = json_response(conn_three, 200)
refute id == third_id
end
test "replying to a status", %{conn: conn} do
user = insert(:user)
{:ok, replied_to} = CommonAPI.post(user, %{"status" => "cofe"})
conn =
conn
|> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
activity = Activity.get_by_id(id)
assert activity.data["context"] == replied_to.data["context"]
assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
end
test "replying to a direct message with visibility other than direct", %{conn: conn} do
user = insert(:user)
{:ok, replied_to} = CommonAPI.post(user, %{"status" => "suya..", "visibility" => "direct"})
Enum.each(["public", "private", "unlisted"], fn visibility ->
conn =
conn
|> post("/api/v1/statuses", %{
"status" => "@#{user.nickname} hey",
"in_reply_to_id" => replied_to.id,
"visibility" => visibility
})
assert json_response(conn, 422) == %{"error" => "The message visibility must be direct"}
end)
end
test "posting a status with an invalid in_reply_to_id", %{conn: conn} do
conn =
conn
|> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => ""})
assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
assert Activity.get_by_id(id)
end
test "posting a sensitive status", %{conn: conn} do
conn =
conn
|> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true})
assert %{"content" => "cofe", "id" => id, "sensitive" => true} = json_response(conn, 200)
assert Activity.get_by_id(id)
end
test "posting a fake status", %{conn: conn} do
real_conn =
conn
|> post("/api/v1/statuses", %{
"status" =>
"\"Tenshi Eating a Corndog\" is a much discussed concept on /jp/. The significance of it is disputed, so I will focus on one core concept: the symbolism behind it"
})
real_status = json_response(real_conn, 200)
assert real_status
assert Object.get_by_ap_id(real_status["uri"])
real_status =
real_status
|> Map.put("id", nil)
|> Map.put("url", nil)
|> Map.put("uri", nil)
|> Map.put("created_at", nil)
|> Kernel.put_in(["pleroma", "conversation_id"], nil)
fake_conn =
conn
|> post("/api/v1/statuses", %{
"status" =>
"\"Tenshi Eating a Corndog\" is a much discussed concept on /jp/. The significance of it is disputed, so I will focus on one core concept: the symbolism behind it",
"preview" => true
})
fake_status = json_response(fake_conn, 200)
assert fake_status
refute Object.get_by_ap_id(fake_status["uri"])
fake_status =
fake_status
|> Map.put("id", nil)
|> Map.put("url", nil)
|> Map.put("uri", nil)
|> Map.put("created_at", nil)
|> Kernel.put_in(["pleroma", "conversation_id"], nil)
assert real_status == fake_status
end
test "posting a status with OGP link preview", %{conn: conn} do
Pleroma.Config.put([:rich_media, :enabled], true)
conn =
conn
|> post("/api/v1/statuses", %{
"status" => "https://example.com/ogp"
})
assert %{"id" => id, "card" => %{"title" => "The Rock"}} = json_response(conn, 200)
assert Activity.get_by_id(id)
Pleroma.Config.put([:rich_media, :enabled], false)
end
test "posting a direct status", %{conn: conn} do
user2 = insert(:user)
content = "direct cofe @#{user2.nickname}"
conn =
conn
|> post("api/v1/statuses", %{"status" => content, "visibility" => "direct"})
assert %{"id" => id, "visibility" => "direct"} = json_response(conn, 200)
assert activity = Activity.get_by_id(id)
assert activity.recipients == [user2.ap_id, conn.assigns[:user].ap_id]
assert activity.data["to"] == [user2.ap_id]
assert activity.data["cc"] == []
end
end
describe "posting polls" do
test "posting a poll", %{conn: conn} do
user = insert(:user)
time = NaiveDateTime.utc_now()
conn =
conn
|> assign(:user, user)
|> post("/api/v1/statuses", %{
"status" => "Who is the #bestgrill?",
"poll" => %{"options" => ["Rei", "Asuka", "Misato"], "expires_in" => 420}
})
response = json_response(conn, 200)
assert Enum.all?(response["poll"]["options"], fn %{"title" => title} ->
title in ["Rei", "Asuka", "Misato"]
end)
assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430
refute response["poll"]["expred"]
end
test "option limit is enforced", %{conn: conn} do
user = insert(:user)
limit = Pleroma.Config.get([:instance, :poll_limits, :max_options])
conn =
conn
|> assign(:user, user)
|> post("/api/v1/statuses", %{
"status" => "desu~",
"poll" => %{"options" => Enum.map(0..limit, fn _ -> "desu" end), "expires_in" => 1}
})
%{"error" => error} = json_response(conn, 422)
assert error == "Poll can't contain more than #{limit} options"
end
test "option character limit is enforced", %{conn: conn} do
user = insert(:user)
limit = Pleroma.Config.get([:instance, :poll_limits, :max_option_chars])
conn =
conn
|> assign(:user, user)
|> post("/api/v1/statuses", %{
"status" => "...",
"poll" => %{
"options" => [Enum.reduce(0..limit, "", fn _, acc -> acc <> "." end)],
"expires_in" => 1
}
})
%{"error" => error} = json_response(conn, 422)
assert error == "Poll options cannot be longer than #{limit} characters each"
end
test "minimal date limit is enforced", %{conn: conn} do
user = insert(:user)
limit = Pleroma.Config.get([:instance, :poll_limits, :min_expiration])
conn =
conn
|> assign(:user, user)
|> post("/api/v1/statuses", %{
"status" => "imagine arbitrary limits",
"poll" => %{
"options" => ["this post was made by pleroma gang"],
"expires_in" => limit - 1
}
})
%{"error" => error} = json_response(conn, 422)
assert error == "Expiration date is too soon"
end
test "maximum date limit is enforced", %{conn: conn} do
user = insert(:user)
limit = Pleroma.Config.get([:instance, :poll_limits, :max_expiration])
conn =
conn
|> assign(:user, user)
|> post("/api/v1/statuses", %{
"status" => "imagine arbitrary limits",
"poll" => %{
"options" => ["this post was made by pleroma gang"],
"expires_in" => limit + 1
}
})
%{"error" => error} = json_response(conn, 422)
assert error == "Expiration date is too far in the future"
end
end
test "direct timeline", %{conn: conn} do
user_one = insert(:user)
user_two = insert(:user)
{:ok, user_two} = User.follow(user_two, user_one)
{:ok, direct} =
CommonAPI.post(user_one, %{
"status" => "Hi @#{user_two.nickname}!",
"visibility" => "direct"
})
{:ok, _follower_only} =
CommonAPI.post(user_one, %{
"status" => "Hi @#{user_two.nickname}!",
"visibility" => "private"
})
# Only direct should be visible here
res_conn =
conn
|> assign(:user, user_two)
|> get("api/v1/timelines/direct")
[status] = json_response(res_conn, 200)
assert %{"visibility" => "direct"} = status
assert status["url"] != direct.data["id"]
# User should be able to see his own direct message
res_conn =
build_conn()
|> assign(:user, user_one)
|> get("api/v1/timelines/direct")
[status] = json_response(res_conn, 200)
assert %{"visibility" => "direct"} = status
# Both should be visible here
res_conn =
conn
|> assign(:user, user_two)
|> get("api/v1/timelines/home")
[_s1, _s2] = json_response(res_conn, 200)
# Test pagination
Enum.each(1..20, fn _ ->
{:ok, _} =
CommonAPI.post(user_one, %{
"status" => "Hi @#{user_two.nickname}!",
"visibility" => "direct"
})
end)
res_conn =
conn
|> assign(:user, user_two)
|> get("api/v1/timelines/direct")
statuses = json_response(res_conn, 200)
assert length(statuses) == 20
res_conn =
conn
|> assign(:user, user_two)
|> get("api/v1/timelines/direct", %{max_id: List.last(statuses)["id"]})
[status] = json_response(res_conn, 200)
assert status["url"] != direct.data["id"]
end
test "Conversations", %{conn: conn} do
user_one = insert(:user)
user_two = insert(:user)
user_three = insert(:user)
{:ok, user_two} = User.follow(user_two, user_one)
{:ok, direct} =
CommonAPI.post(user_one, %{
"status" => "Hi @#{user_two.nickname}, @#{user_three.nickname}!",
"visibility" => "direct"
})
{:ok, _follower_only} =
CommonAPI.post(user_one, %{
"status" => "Hi @#{user_two.nickname}!",
"visibility" => "private"
})
res_conn =
conn
|> assign(:user, user_one)
|> get("/api/v1/conversations")
assert response = json_response(res_conn, 200)
assert [
%{
"id" => res_id,
"accounts" => res_accounts,
"last_status" => res_last_status,
"unread" => unread
}
] = response
account_ids = Enum.map(res_accounts, & &1["id"])
assert length(res_accounts) == 2
assert user_two.id in account_ids
assert user_three.id in account_ids
assert is_binary(res_id)
assert unread == true
assert res_last_status["id"] == direct.id
# Apparently undocumented API endpoint
res_conn =
conn
|> assign(:user, user_one)
|> post("/api/v1/conversations/#{res_id}/read")
assert response = json_response(res_conn, 200)
assert length(response["accounts"]) == 2
assert response["last_status"]["id"] == direct.id
assert response["unread"] == false
# (vanilla) Mastodon frontend behaviour
res_conn =
conn
|> assign(:user, user_one)
|> get("/api/v1/statuses/#{res_last_status["id"]}/context")
assert %{"ancestors" => [], "descendants" => []} == json_response(res_conn, 200)
end
test "doesn't include DMs from blocked users", %{conn: conn} do
blocker = insert(:user)
blocked = insert(:user)
user = insert(:user)
{:ok, blocker} = User.block(blocker, blocked)
{:ok, _blocked_direct} =
CommonAPI.post(blocked, %{
"status" => "Hi @#{blocker.nickname}!",
"visibility" => "direct"
})
{:ok, direct} =
CommonAPI.post(user, %{
"status" => "Hi @#{blocker.nickname}!",
"visibility" => "direct"
})
res_conn =
conn
|> assign(:user, user)
|> get("api/v1/timelines/direct")
[status] = json_response(res_conn, 200)
assert status["id"] == direct.id
end
test "verify_credentials", %{conn: conn} do
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> get("/api/v1/accounts/verify_credentials")
response = json_response(conn, 200)
assert %{"id" => id, "source" => %{"privacy" => "public"}} = response
assert response["pleroma"]["chat_token"]
assert id == to_string(user.id)
end
test "verify_credentials default scope unlisted", %{conn: conn} do
user = insert(:user, %{info: %User.Info{default_scope: "unlisted"}})
conn =
conn
|> assign(:user, user)
|> get("/api/v1/accounts/verify_credentials")
assert %{"id" => id, "source" => %{"privacy" => "unlisted"}} = json_response(conn, 200)
assert id == to_string(user.id)
end
test "apps/verify_credentials", %{conn: conn} do
token = insert(:oauth_token)
conn =
conn
|> assign(:user, token.user)
|> assign(:token, token)
|> get("/api/v1/apps/verify_credentials")
app = Repo.preload(token, :app).app
expected = %{
"name" => app.client_name,
"website" => app.website,
"vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
}
assert expected == json_response(conn, 200)
end
test "user avatar can be set", %{conn: conn} do
user = insert(:user)
avatar_image = File.read!("test/fixtures/avatar_data_uri")
conn =
conn
|> assign(:user, user)
|> patch("/api/v1/pleroma/accounts/update_avatar", %{img: avatar_image})
user = refresh_record(user)
assert %{
"name" => _,
"type" => _,
"url" => [
%{
"href" => _,
"mediaType" => _,
"type" => _
}
]
} = user.avatar
assert %{"url" => _} = json_response(conn, 200)
end
test "user avatar can be reset", %{conn: conn} do
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> patch("/api/v1/pleroma/accounts/update_avatar", %{img: ""})
user = User.get_cached_by_id(user.id)
assert user.avatar == nil
assert %{"url" => nil} = json_response(conn, 200)
end
test "can set profile banner", %{conn: conn} do
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> patch("/api/v1/pleroma/accounts/update_banner", %{"banner" => @image})
user = refresh_record(user)
assert user.info.banner["type"] == "Image"
assert %{"url" => _} = json_response(conn, 200)
end
test "can reset profile banner", %{conn: conn} do
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> patch("/api/v1/pleroma/accounts/update_banner", %{"banner" => ""})
user = refresh_record(user)
assert user.info.banner == %{}
assert %{"url" => nil} = json_response(conn, 200)
end
test "background image can be set", %{conn: conn} do
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> patch("/api/v1/pleroma/accounts/update_background", %{"img" => @image})
user = refresh_record(user)
assert user.info.background["type"] == "Image"
assert %{"url" => _} = json_response(conn, 200)
end
test "background image can be reset", %{conn: conn} do
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> patch("/api/v1/pleroma/accounts/update_background", %{"img" => ""})
user = refresh_record(user)
assert user.info.background == %{}
assert %{"url" => nil} = json_response(conn, 200)
end
test "creates an oauth app", %{conn: conn} do
user = insert(:user)
app_attrs = build(:oauth_app)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/apps", %{
client_name: app_attrs.client_name,
redirect_uris: app_attrs.redirect_uris
})
[app] = Repo.all(App)
expected = %{
"name" => app.client_name,
"website" => app.website,
"client_id" => app.client_id,
"client_secret" => app.client_secret,
"id" => app.id |> to_string(),
"redirect_uri" => app.redirect_uris,
"vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
}
assert expected == json_response(conn, 200)
end
test "get a status", %{conn: conn} do
activity = insert(:note_activity)
conn =
conn
|> get("/api/v1/statuses/#{activity.id}")
assert %{"id" => id} = json_response(conn, 200)
assert id == to_string(activity.id)
end
describe "deleting a status" do
test "when you created it", %{conn: conn} do
activity = insert(:note_activity)
author = User.get_cached_by_ap_id(activity.data["actor"])
conn =
conn
|> assign(:user, author)
|> delete("/api/v1/statuses/#{activity.id}")
assert %{} = json_response(conn, 200)
refute Activity.get_by_id(activity.id)
end
test "when you didn't create it", %{conn: conn} do
activity = insert(:note_activity)
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> delete("/api/v1/statuses/#{activity.id}")
assert %{"error" => _} = json_response(conn, 403)
assert Activity.get_by_id(activity.id) == activity
end
test "when you're an admin or moderator", %{conn: conn} do
activity1 = insert(:note_activity)
activity2 = insert(:note_activity)
admin = insert(:user, info: %{is_admin: true})
moderator = insert(:user, info: %{is_moderator: true})
res_conn =
conn
|> assign(:user, admin)
|> delete("/api/v1/statuses/#{activity1.id}")
assert %{} = json_response(res_conn, 200)
res_conn =
conn
|> assign(:user, moderator)
|> delete("/api/v1/statuses/#{activity2.id}")
assert %{} = json_response(res_conn, 200)
refute Activity.get_by_id(activity1.id)
refute Activity.get_by_id(activity2.id)
end
end
describe "filters" do
test "creating a filter", %{conn: conn} do
user = insert(:user)
filter = %Pleroma.Filter{
phrase: "knights",
context: ["home"]
}
conn =
conn
|> assign(:user, user)
|> post("/api/v1/filters", %{"phrase" => filter.phrase, context: filter.context})
assert response = json_response(conn, 200)
assert response["phrase"] == filter.phrase
assert response["context"] == filter.context
assert response["irreversible"] == false
assert response["id"] != nil
assert response["id"] != ""
end
test "fetching a list of filters", %{conn: conn} do
user = insert(:user)
query_one = %Pleroma.Filter{
user_id: user.id,
filter_id: 1,
phrase: "knights",
context: ["home"]
}
query_two = %Pleroma.Filter{
user_id: user.id,
filter_id: 2,
phrase: "who",
context: ["home"]
}
{:ok, filter_one} = Pleroma.Filter.create(query_one)
{:ok, filter_two} = Pleroma.Filter.create(query_two)
response =
conn
|> assign(:user, user)
|> get("/api/v1/filters")
|> json_response(200)
assert response ==
render_json(
FilterView,
"filters.json",
filters: [filter_two, filter_one]
)
end
test "get a filter", %{conn: conn} do
user = insert(:user)
query = %Pleroma.Filter{
user_id: user.id,
filter_id: 2,
phrase: "knight",
context: ["home"]
}
{:ok, filter} = Pleroma.Filter.create(query)
conn =
conn
|> assign(:user, user)
|> get("/api/v1/filters/#{filter.filter_id}")
assert _response = json_response(conn, 200)
end
test "update a filter", %{conn: conn} do
user = insert(:user)
query = %Pleroma.Filter{
user_id: user.id,
filter_id: 2,
phrase: "knight",
context: ["home"]
}
{:ok, _filter} = Pleroma.Filter.create(query)
new = %Pleroma.Filter{
phrase: "nii",
context: ["home"]
}
conn =
conn
|> assign(:user, user)
|> put("/api/v1/filters/#{query.filter_id}", %{
phrase: new.phrase,
context: new.context
})
assert response = json_response(conn, 200)
assert response["phrase"] == new.phrase
assert response["context"] == new.context
end
test "delete a filter", %{conn: conn} do
user = insert(:user)
query = %Pleroma.Filter{
user_id: user.id,
filter_id: 2,
phrase: "knight",
context: ["home"]
}
{:ok, filter} = Pleroma.Filter.create(query)
conn =
conn
|> assign(:user, user)
|> delete("/api/v1/filters/#{filter.filter_id}")
assert response = json_response(conn, 200)
assert response == %{}
end
end
describe "lists" do
test "creating a list", %{conn: conn} do
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/lists", %{"title" => "cuties"})
assert %{"title" => title} = json_response(conn, 200)
assert title == "cuties"
end
test "adding users to a list", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, list} = Pleroma.List.create("name", user)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
assert %{} == json_response(conn, 200)
%Pleroma.List{following: following} = Pleroma.List.get(list.id, user)
assert following == [other_user.follower_address]
end
test "removing users from a list", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
third_user = insert(:user)
{:ok, list} = Pleroma.List.create("name", user)
{:ok, list} = Pleroma.List.follow(list, other_user)
{:ok, list} = Pleroma.List.follow(list, third_user)
conn =
conn
|> assign(:user, user)
|> delete("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
assert %{} == json_response(conn, 200)
%Pleroma.List{following: following} = Pleroma.List.get(list.id, user)
assert following == [third_user.follower_address]
end
test "listing users in a list", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, list} = Pleroma.List.create("name", user)
{:ok, list} = Pleroma.List.follow(list, other_user)
conn =
conn
|> assign(:user, user)
|> get("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
assert [%{"id" => id}] = json_response(conn, 200)
assert id == to_string(other_user.id)
end
test "retrieving a list", %{conn: conn} do
user = insert(:user)
{:ok, list} = Pleroma.List.create("name", user)
conn =
conn
|> assign(:user, user)
|> get("/api/v1/lists/#{list.id}")
assert %{"id" => id} = json_response(conn, 200)
assert id == to_string(list.id)
end
test "renaming a list", %{conn: conn} do
user = insert(:user)
{:ok, list} = Pleroma.List.create("name", user)
conn =
conn
|> assign(:user, user)
|> put("/api/v1/lists/#{list.id}", %{"title" => "newname"})
assert %{"title" => name} = json_response(conn, 200)
assert name == "newname"
end
test "deleting a list", %{conn: conn} do
user = insert(:user)
{:ok, list} = Pleroma.List.create("name", user)
conn =
conn
|> assign(:user, user)
|> delete("/api/v1/lists/#{list.id}")
assert %{} = json_response(conn, 200)
assert is_nil(Repo.get(Pleroma.List, list.id))
end
test "list timeline", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, _activity_one} = TwitterAPI.create_status(user, %{"status" => "Marisa is cute."})
{:ok, activity_two} = TwitterAPI.create_status(other_user, %{"status" => "Marisa is cute."})
{:ok, list} = Pleroma.List.create("name", user)
{:ok, list} = Pleroma.List.follow(list, other_user)
conn =
conn
|> assign(:user, user)
|> get("/api/v1/timelines/list/#{list.id}")
assert [%{"id" => id}] = json_response(conn, 200)
assert id == to_string(activity_two.id)
end
test "list timeline does not leak non-public statuses for unfollowed users", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, activity_one} = TwitterAPI.create_status(other_user, %{"status" => "Marisa is cute."})
{:ok, _activity_two} =
TwitterAPI.create_status(other_user, %{
"status" => "Marisa is cute.",
"visibility" => "private"
})
{:ok, list} = Pleroma.List.create("name", user)
{:ok, list} = Pleroma.List.follow(list, other_user)
conn =
conn
|> assign(:user, user)
|> get("/api/v1/timelines/list/#{list.id}")
assert [%{"id" => id}] = json_response(conn, 200)
assert id == to_string(activity_one.id)
end
end
describe "notifications" do
test "list of notifications", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} =
TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
{:ok, [_notification]} = Notification.create_notifications(activity)
conn =
conn
|> assign(:user, user)
|> get("/api/v1/notifications")
expected_response =
"hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{
user.ap_id
}\">@<span>#{user.nickname}</span></a></span>"
assert [%{"status" => %{"content" => response}} | _rest] = json_response(conn, 200)
assert response == expected_response
end
test "getting a single notification", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} =
TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
{:ok, [notification]} = Notification.create_notifications(activity)
conn =
conn
|> assign(:user, user)
|> get("/api/v1/notifications/#{notification.id}")
expected_response =
"hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{
user.ap_id
}\">@<span>#{user.nickname}</span></a></span>"
assert %{"status" => %{"content" => response}} = json_response(conn, 200)
assert response == expected_response
end
test "dismissing a single notification", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} =
TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
{:ok, [notification]} = Notification.create_notifications(activity)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/notifications/dismiss", %{"id" => notification.id})
assert %{} = json_response(conn, 200)
end
test "clearing all notifications", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} =
TwitterAPI.create_status(other_user, %{"status" => "hi @#{user.nickname}"})
{:ok, [_notification]} = Notification.create_notifications(activity)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/notifications/clear")
assert %{} = json_response(conn, 200)
conn =
build_conn()
|> assign(:user, user)
|> get("/api/v1/notifications")
assert all = json_response(conn, 200)
assert all == []
end
test "paginates notifications using min_id, since_id, max_id, and limit", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
{:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
{:ok, activity3} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
{:ok, activity4} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
notification1_id = Repo.get_by(Notification, activity_id: activity1.id).id |> to_string()
notification2_id = Repo.get_by(Notification, activity_id: activity2.id).id |> to_string()
notification3_id = Repo.get_by(Notification, activity_id: activity3.id).id |> to_string()
notification4_id = Repo.get_by(Notification, activity_id: activity4.id).id |> to_string()
conn =
conn
|> assign(:user, user)
# min_id
conn_res =
conn
|> get("/api/v1/notifications?limit=2&min_id=#{notification1_id}")
result = json_response(conn_res, 200)
assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
# since_id
conn_res =
conn
|> get("/api/v1/notifications?limit=2&since_id=#{notification1_id}")
result = json_response(conn_res, 200)
assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
# max_id
conn_res =
conn
|> get("/api/v1/notifications?limit=2&max_id=#{notification4_id}")
result = json_response(conn_res, 200)
assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
end
test "filters notifications using exclude_types", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, mention_activity} = CommonAPI.post(other_user, %{"status" => "hey @#{user.nickname}"})
{:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"})
{:ok, favorite_activity, _} = CommonAPI.favorite(create_activity.id, other_user)
{:ok, reblog_activity, _} = CommonAPI.repeat(create_activity.id, other_user)
{:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user)
mention_notification_id =
Repo.get_by(Notification, activity_id: mention_activity.id).id |> to_string()
favorite_notification_id =
Repo.get_by(Notification, activity_id: favorite_activity.id).id |> to_string()
reblog_notification_id =
Repo.get_by(Notification, activity_id: reblog_activity.id).id |> to_string()
follow_notification_id =
Repo.get_by(Notification, activity_id: follow_activity.id).id |> to_string()
conn =
conn
|> assign(:user, user)
conn_res =
get(conn, "/api/v1/notifications", %{exclude_types: ["mention", "favourite", "reblog"]})
assert [%{"id" => ^follow_notification_id}] = json_response(conn_res, 200)
conn_res =
get(conn, "/api/v1/notifications", %{exclude_types: ["favourite", "reblog", "follow"]})
assert [%{"id" => ^mention_notification_id}] = json_response(conn_res, 200)
conn_res =
get(conn, "/api/v1/notifications", %{exclude_types: ["reblog", "follow", "mention"]})
assert [%{"id" => ^favorite_notification_id}] = json_response(conn_res, 200)
conn_res =
get(conn, "/api/v1/notifications", %{exclude_types: ["follow", "mention", "favourite"]})
assert [%{"id" => ^reblog_notification_id}] = json_response(conn_res, 200)
end
test "destroy multiple", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
{:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
{:ok, activity3} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"})
{:ok, activity4} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"})
notification1_id = Repo.get_by(Notification, activity_id: activity1.id).id |> to_string()
notification2_id = Repo.get_by(Notification, activity_id: activity2.id).id |> to_string()
notification3_id = Repo.get_by(Notification, activity_id: activity3.id).id |> to_string()
notification4_id = Repo.get_by(Notification, activity_id: activity4.id).id |> to_string()
conn =
conn
|> assign(:user, user)
conn_res =
conn
|> get("/api/v1/notifications")
result = json_response(conn_res, 200)
assert [%{"id" => ^notification2_id}, %{"id" => ^notification1_id}] = result
conn2 =
conn
|> assign(:user, other_user)
conn_res =
conn2
|> get("/api/v1/notifications")
result = json_response(conn_res, 200)
assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
conn_destroy =
conn
|> delete("/api/v1/notifications/destroy_multiple", %{
"ids" => [notification1_id, notification2_id]
})
assert json_response(conn_destroy, 200) == %{}
conn_res =
conn2
|> get("/api/v1/notifications")
result = json_response(conn_res, 200)
assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
end
test "doesn't see notifications after muting user with notifications", %{conn: conn} do
user = insert(:user)
user2 = insert(:user)
{:ok, _, _, _} = CommonAPI.follow(user, user2)
{:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"})
conn = assign(conn, :user, user)
conn = get(conn, "/api/v1/notifications")
assert length(json_response(conn, 200)) == 1
{:ok, user} = User.mute(user, user2)
conn = assign(build_conn(), :user, user)
conn = get(conn, "/api/v1/notifications")
assert json_response(conn, 200) == []
end
test "see notifications after muting user without notifications", %{conn: conn} do
user = insert(:user)
user2 = insert(:user)
{:ok, _, _, _} = CommonAPI.follow(user, user2)
{:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"})
conn = assign(conn, :user, user)
conn = get(conn, "/api/v1/notifications")
assert length(json_response(conn, 200)) == 1
{:ok, user} = User.mute(user, user2, false)
conn = assign(build_conn(), :user, user)
conn = get(conn, "/api/v1/notifications")
assert length(json_response(conn, 200)) == 1
end
+
+ test "see notifications after muting user with notifications and with_muted parameter", %{
+ conn: conn
+ } do
+ user = insert(:user)
+ user2 = insert(:user)
+
+ {:ok, _, _, _} = CommonAPI.follow(user, user2)
+ {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"})
+
+ conn = assign(conn, :user, user)
+
+ conn = get(conn, "/api/v1/notifications")
+
+ assert length(json_response(conn, 200)) == 1
+
+ {:ok, user} = User.mute(user, user2)
+
+ conn = assign(build_conn(), :user, user)
+ conn = get(conn, "/api/v1/notifications", %{"with_muted" => "true"})
+
+ assert length(json_response(conn, 200)) == 1
+ end
end
describe "reblogging" do
test "reblogs and returns the reblogged status", %{conn: conn} do
activity = insert(:note_activity)
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/statuses/#{activity.id}/reblog")
assert %{
"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1},
"reblogged" => true
} = json_response(conn, 200)
assert to_string(activity.id) == id
end
test "reblogged status for another user", %{conn: conn} do
activity = insert(:note_activity)
user1 = insert(:user)
user2 = insert(:user)
user3 = insert(:user)
CommonAPI.favorite(activity.id, user2)
{:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id)
{:ok, reblog_activity1, _object} = CommonAPI.repeat(activity.id, user1)
{:ok, _, _object} = CommonAPI.repeat(activity.id, user2)
conn_res =
conn
|> assign(:user, user3)
|> get("/api/v1/statuses/#{reblog_activity1.id}")
assert %{
"reblog" => %{"id" => id, "reblogged" => false, "reblogs_count" => 2},
"reblogged" => false,
"favourited" => false,
"bookmarked" => false
} = json_response(conn_res, 200)
conn_res =
conn
|> assign(:user, user2)
|> get("/api/v1/statuses/#{reblog_activity1.id}")
assert %{
"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 2},
"reblogged" => true,
"favourited" => true,
"bookmarked" => true
} = json_response(conn_res, 200)
assert to_string(activity.id) == id
end
end
describe "unreblogging" do
test "unreblogs and returns the unreblogged status", %{conn: conn} do
activity = insert(:note_activity)
user = insert(:user)
{:ok, _, _} = CommonAPI.repeat(activity.id, user)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/statuses/#{activity.id}/unreblog")
assert %{"id" => id, "reblogged" => false, "reblogs_count" => 0} = json_response(conn, 200)
assert to_string(activity.id) == id
end
end
describe "favoriting" do
test "favs a status and returns it", %{conn: conn} do
activity = insert(:note_activity)
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/statuses/#{activity.id}/favourite")
assert %{"id" => id, "favourites_count" => 1, "favourited" => true} =
json_response(conn, 200)
assert to_string(activity.id) == id
end
test "returns 500 for a wrong id", %{conn: conn} do
user = insert(:user)
resp =
conn
|> assign(:user, user)
|> post("/api/v1/statuses/1/favourite")
|> json_response(500)
assert resp == "Something went wrong"
end
end
describe "unfavoriting" do
test "unfavorites a status and returns it", %{conn: conn} do
activity = insert(:note_activity)
user = insert(:user)
{:ok, _, _} = CommonAPI.favorite(activity.id, user)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/statuses/#{activity.id}/unfavourite")
assert %{"id" => id, "favourites_count" => 0, "favourited" => false} =
json_response(conn, 200)
assert to_string(activity.id) == id
end
end
describe "user timelines" do
test "gets a users statuses", %{conn: conn} do
user_one = insert(:user)
user_two = insert(:user)
user_three = insert(:user)
{:ok, user_three} = User.follow(user_three, user_one)
{:ok, activity} = CommonAPI.post(user_one, %{"status" => "HI!!!"})
{:ok, direct_activity} =
CommonAPI.post(user_one, %{
"status" => "Hi, @#{user_two.nickname}.",
"visibility" => "direct"
})
{:ok, private_activity} =
CommonAPI.post(user_one, %{"status" => "private", "visibility" => "private"})
resp =
conn
|> get("/api/v1/accounts/#{user_one.id}/statuses")
assert [%{"id" => id}] = json_response(resp, 200)
assert id == to_string(activity.id)
resp =
conn
|> assign(:user, user_two)
|> get("/api/v1/accounts/#{user_one.id}/statuses")
assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200)
assert id_one == to_string(direct_activity.id)
assert id_two == to_string(activity.id)
resp =
conn
|> assign(:user, user_three)
|> get("/api/v1/accounts/#{user_one.id}/statuses")
assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200)
assert id_one == to_string(private_activity.id)
assert id_two == to_string(activity.id)
end
test "unimplemented pinned statuses feature", %{conn: conn} do
note = insert(:note_activity)
user = User.get_cached_by_ap_id(note.data["actor"])
conn =
conn
|> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
assert json_response(conn, 200) == []
end
test "gets an users media", %{conn: conn} do
note = insert(:note_activity)
user = User.get_cached_by_ap_id(note.data["actor"])
file = %Plug.Upload{
content_type: "image/jpg",
path: Path.absname("test/fixtures/image.jpg"),
filename: "an_image.jpg"
}
media =
TwitterAPI.upload(file, user, "json")
|> Poison.decode!()
{:ok, image_post} =
TwitterAPI.create_status(user, %{"status" => "cofe", "media_ids" => [media["media_id"]]})
conn =
conn
|> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "true"})
assert [%{"id" => id}] = json_response(conn, 200)
assert id == to_string(image_post.id)
conn =
build_conn()
|> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "1"})
assert [%{"id" => id}] = json_response(conn, 200)
assert id == to_string(image_post.id)
end
test "gets a user's statuses without reblogs", %{conn: conn} do
user = insert(:user)
{:ok, post} = CommonAPI.post(user, %{"status" => "HI!!!"})
{:ok, _, _} = CommonAPI.repeat(post.id, user)
conn =
conn
|> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "true"})
assert [%{"id" => id}] = json_response(conn, 200)
assert id == to_string(post.id)
conn =
conn
|> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "1"})
assert [%{"id" => id}] = json_response(conn, 200)
assert id == to_string(post.id)
end
test "filters user's statuses by a hashtag", %{conn: conn} do
user = insert(:user)
{:ok, post} = CommonAPI.post(user, %{"status" => "#hashtag"})
{:ok, _post} = CommonAPI.post(user, %{"status" => "hashtag"})
conn =
conn
|> get("/api/v1/accounts/#{user.id}/statuses", %{"tagged" => "hashtag"})
assert [%{"id" => id}] = json_response(conn, 200)
assert id == to_string(post.id)
end
end
describe "user relationships" do
test "returns the relationships for the current user", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, user} = User.follow(user, other_user)
conn =
conn
|> assign(:user, user)
|> get("/api/v1/accounts/relationships", %{"id" => [other_user.id]})
assert [relationship] = json_response(conn, 200)
assert to_string(other_user.id) == relationship["id"]
end
end
describe "media upload" do
setup do
upload_config = Pleroma.Config.get([Pleroma.Upload])
proxy_config = Pleroma.Config.get([:media_proxy])
on_exit(fn ->
Pleroma.Config.put([Pleroma.Upload], upload_config)
Pleroma.Config.put([:media_proxy], proxy_config)
end)
user = insert(:user)
conn =
build_conn()
|> assign(:user, user)
image = %Plug.Upload{
content_type: "image/jpg",
path: Path.absname("test/fixtures/image.jpg"),
filename: "an_image.jpg"
}
[conn: conn, image: image]
end
test "returns uploaded image", %{conn: conn, image: image} do
desc = "Description of the image"
media =
conn
|> post("/api/v1/media", %{"file" => image, "description" => desc})
|> json_response(:ok)
assert media["type"] == "image"
assert media["description"] == desc
assert media["id"]
object = Repo.get(Object, media["id"])
assert object.data["actor"] == User.ap_id(conn.assigns[:user])
end
test "returns proxied url when media proxy is enabled", %{conn: conn, image: image} do
Pleroma.Config.put([Pleroma.Upload, :base_url], "https://media.pleroma.social")
proxy_url = "https://cache.pleroma.social"
Pleroma.Config.put([:media_proxy, :enabled], true)
Pleroma.Config.put([:media_proxy, :base_url], proxy_url)
media =
conn
|> post("/api/v1/media", %{"file" => image})
|> json_response(:ok)
assert String.starts_with?(media["url"], proxy_url)
end
test "returns media url when proxy is enabled but media url is whitelisted", %{
conn: conn,
image: image
} do
media_url = "https://media.pleroma.social"
Pleroma.Config.put([Pleroma.Upload, :base_url], media_url)
Pleroma.Config.put([:media_proxy, :enabled], true)
Pleroma.Config.put([:media_proxy, :base_url], "https://cache.pleroma.social")
Pleroma.Config.put([:media_proxy, :whitelist], ["media.pleroma.social"])
media =
conn
|> post("/api/v1/media", %{"file" => image})
|> json_response(:ok)
assert String.starts_with?(media["url"], media_url)
end
end
describe "locked accounts" do
test "/api/v1/follow_requests works" do
user = insert(:user, %{info: %User.Info{locked: true}})
other_user = insert(:user)
{:ok, _activity} = ActivityPub.follow(other_user, user)
user = User.get_cached_by_id(user.id)
other_user = User.get_cached_by_id(other_user.id)
assert User.following?(other_user, user) == false
conn =
build_conn()
|> assign(:user, user)
|> get("/api/v1/follow_requests")
assert [relationship] = json_response(conn, 200)
assert to_string(other_user.id) == relationship["id"]
end
test "/api/v1/follow_requests/:id/authorize works" do
user = insert(:user, %{info: %User.Info{locked: true}})
other_user = insert(:user)
{:ok, _activity} = ActivityPub.follow(other_user, user)
user = User.get_cached_by_id(user.id)
other_user = User.get_cached_by_id(other_user.id)
assert User.following?(other_user, user) == false
conn =
build_conn()
|> assign(:user, user)
|> post("/api/v1/follow_requests/#{other_user.id}/authorize")
assert relationship = json_response(conn, 200)
assert to_string(other_user.id) == relationship["id"]
user = User.get_cached_by_id(user.id)
other_user = User.get_cached_by_id(other_user.id)
assert User.following?(other_user, user) == true
end
test "verify_credentials", %{conn: conn} do
user = insert(:user, %{info: %User.Info{default_scope: "private"}})
conn =
conn
|> assign(:user, user)
|> get("/api/v1/accounts/verify_credentials")
assert %{"id" => id, "source" => %{"privacy" => "private"}} = json_response(conn, 200)
assert id == to_string(user.id)
end
test "/api/v1/follow_requests/:id/reject works" do
user = insert(:user, %{info: %User.Info{locked: true}})
other_user = insert(:user)
{:ok, _activity} = ActivityPub.follow(other_user, user)
user = User.get_cached_by_id(user.id)
conn =
build_conn()
|> assign(:user, user)
|> post("/api/v1/follow_requests/#{other_user.id}/reject")
assert relationship = json_response(conn, 200)
assert to_string(other_user.id) == relationship["id"]
user = User.get_cached_by_id(user.id)
other_user = User.get_cached_by_id(other_user.id)
assert User.following?(other_user, user) == false
end
end
test "account fetching", %{conn: conn} do
user = insert(:user)
conn =
conn
|> get("/api/v1/accounts/#{user.id}")
assert %{"id" => id} = json_response(conn, 200)
assert id == to_string(user.id)
conn =
build_conn()
|> get("/api/v1/accounts/-1")
assert %{"error" => "Can't find user"} = json_response(conn, 404)
end
test "account fetching also works nickname", %{conn: conn} do
user = insert(:user)
conn =
conn
|> get("/api/v1/accounts/#{user.nickname}")
assert %{"id" => id} = json_response(conn, 200)
assert id == user.id
end
test "mascot upload", %{conn: conn} do
user = insert(:user)
non_image_file = %Plug.Upload{
content_type: "audio/mpeg",
path: Path.absname("test/fixtures/sound.mp3"),
filename: "sound.mp3"
}
conn =
conn
|> assign(:user, user)
|> put("/api/v1/pleroma/mascot", %{"file" => non_image_file})
assert json_response(conn, 415)
file = %Plug.Upload{
content_type: "image/jpg",
path: Path.absname("test/fixtures/image.jpg"),
filename: "an_image.jpg"
}
conn =
build_conn()
|> assign(:user, user)
|> put("/api/v1/pleroma/mascot", %{"file" => file})
assert %{"id" => _, "type" => image} = json_response(conn, 200)
end
test "mascot retrieving", %{conn: conn} do
user = insert(:user)
# When user hasn't set a mascot, we should just get pleroma tan back
conn =
conn
|> assign(:user, user)
|> get("/api/v1/pleroma/mascot")
assert %{"url" => url} = json_response(conn, 200)
assert url =~ "pleroma-fox-tan-smol"
# When a user sets their mascot, we should get that back
file = %Plug.Upload{
content_type: "image/jpg",
path: Path.absname("test/fixtures/image.jpg"),
filename: "an_image.jpg"
}
conn =
build_conn()
|> assign(:user, user)
|> put("/api/v1/pleroma/mascot", %{"file" => file})
assert json_response(conn, 200)
user = User.get_cached_by_id(user.id)
conn =
build_conn()
|> assign(:user, user)
|> get("/api/v1/pleroma/mascot")
assert %{"url" => url, "type" => "image"} = json_response(conn, 200)
assert url =~ "an_image"
end
test "hashtag timeline", %{conn: conn} do
following = insert(:user)
capture_log(fn ->
{:ok, activity} = TwitterAPI.create_status(following, %{"status" => "test #2hu"})
{:ok, [_activity]} =
OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873")
nconn =
conn
|> get("/api/v1/timelines/tag/2hu")
assert [%{"id" => id}] = json_response(nconn, 200)
assert id == to_string(activity.id)
# works for different capitalization too
nconn =
conn
|> get("/api/v1/timelines/tag/2HU")
assert [%{"id" => id}] = json_response(nconn, 200)
assert id == to_string(activity.id)
end)
end
test "multi-hashtag timeline", %{conn: conn} do
user = insert(:user)
{:ok, activity_test} = CommonAPI.post(user, %{"status" => "#test"})
{:ok, activity_test1} = CommonAPI.post(user, %{"status" => "#test #test1"})
{:ok, activity_none} = CommonAPI.post(user, %{"status" => "#test #none"})
any_test =
conn
|> get("/api/v1/timelines/tag/test", %{"any" => ["test1"]})
[status_none, status_test1, status_test] = json_response(any_test, 200)
assert to_string(activity_test.id) == status_test["id"]
assert to_string(activity_test1.id) == status_test1["id"]
assert to_string(activity_none.id) == status_none["id"]
restricted_test =
conn
|> get("/api/v1/timelines/tag/test", %{"all" => ["test1"], "none" => ["none"]})
assert [status_test1] == json_response(restricted_test, 200)
all_test = conn |> get("/api/v1/timelines/tag/test", %{"all" => ["none"]})
assert [status_none] == json_response(all_test, 200)
end
test "getting followers", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, user} = User.follow(user, other_user)
conn =
conn
|> get("/api/v1/accounts/#{other_user.id}/followers")
assert [%{"id" => id}] = json_response(conn, 200)
assert id == to_string(user.id)
end
test "getting followers, hide_followers", %{conn: conn} do
user = insert(:user)
other_user = insert(:user, %{info: %{hide_followers: true}})
{:ok, _user} = User.follow(user, other_user)
conn =
conn
|> get("/api/v1/accounts/#{other_user.id}/followers")
assert [] == json_response(conn, 200)
end
test "getting followers, hide_followers, same user requesting", %{conn: conn} do
user = insert(:user)
other_user = insert(:user, %{info: %{hide_followers: true}})
{:ok, _user} = User.follow(user, other_user)
conn =
conn
|> assign(:user, other_user)
|> get("/api/v1/accounts/#{other_user.id}/followers")
refute [] == json_response(conn, 200)
end
test "getting followers, pagination", %{conn: conn} do
user = insert(:user)
follower1 = insert(:user)
follower2 = insert(:user)
follower3 = insert(:user)
{:ok, _} = User.follow(follower1, user)
{:ok, _} = User.follow(follower2, user)
{:ok, _} = User.follow(follower3, user)
conn =
conn
|> assign(:user, user)
res_conn =
conn
|> get("/api/v1/accounts/#{user.id}/followers?since_id=#{follower1.id}")
assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200)
assert id3 == follower3.id
assert id2 == follower2.id
res_conn =
conn
|> get("/api/v1/accounts/#{user.id}/followers?max_id=#{follower3.id}")
assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200)
assert id2 == follower2.id
assert id1 == follower1.id
res_conn =
conn
|> get("/api/v1/accounts/#{user.id}/followers?limit=1&max_id=#{follower3.id}")
assert [%{"id" => id2}] = json_response(res_conn, 200)
assert id2 == follower2.id
assert [link_header] = get_resp_header(res_conn, "link")
assert link_header =~ ~r/min_id=#{follower2.id}/
assert link_header =~ ~r/max_id=#{follower2.id}/
end
test "getting following", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, user} = User.follow(user, other_user)
conn =
conn
|> get("/api/v1/accounts/#{user.id}/following")
assert [%{"id" => id}] = json_response(conn, 200)
assert id == to_string(other_user.id)
end
test "getting following, hide_follows", %{conn: conn} do
user = insert(:user, %{info: %{hide_follows: true}})
other_user = insert(:user)
{:ok, user} = User.follow(user, other_user)
conn =
conn
|> get("/api/v1/accounts/#{user.id}/following")
assert [] == json_response(conn, 200)
end
test "getting following, hide_follows, same user requesting", %{conn: conn} do
user = insert(:user, %{info: %{hide_follows: true}})
other_user = insert(:user)
{:ok, user} = User.follow(user, other_user)
conn =
conn
|> assign(:user, user)
|> get("/api/v1/accounts/#{user.id}/following")
refute [] == json_response(conn, 200)
end
test "getting following, pagination", %{conn: conn} do
user = insert(:user)
following1 = insert(:user)
following2 = insert(:user)
following3 = insert(:user)
{:ok, _} = User.follow(user, following1)
{:ok, _} = User.follow(user, following2)
{:ok, _} = User.follow(user, following3)
conn =
conn
|> assign(:user, user)
res_conn =
conn
|> get("/api/v1/accounts/#{user.id}/following?since_id=#{following1.id}")
assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200)
assert id3 == following3.id
assert id2 == following2.id
res_conn =
conn
|> get("/api/v1/accounts/#{user.id}/following?max_id=#{following3.id}")
assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200)
assert id2 == following2.id
assert id1 == following1.id
res_conn =
conn
|> get("/api/v1/accounts/#{user.id}/following?limit=1&max_id=#{following3.id}")
assert [%{"id" => id2}] = json_response(res_conn, 200)
assert id2 == following2.id
assert [link_header] = get_resp_header(res_conn, "link")
assert link_header =~ ~r/min_id=#{following2.id}/
assert link_header =~ ~r/max_id=#{following2.id}/
end
test "following / unfollowing a user", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/accounts/#{other_user.id}/follow")
assert %{"id" => _id, "following" => true} = json_response(conn, 200)
user = User.get_cached_by_id(user.id)
conn =
build_conn()
|> assign(:user, user)
|> post("/api/v1/accounts/#{other_user.id}/unfollow")
assert %{"id" => _id, "following" => false} = json_response(conn, 200)
user = User.get_cached_by_id(user.id)
conn =
build_conn()
|> assign(:user, user)
|> post("/api/v1/follows", %{"uri" => other_user.nickname})
assert %{"id" => id} = json_response(conn, 200)
assert id == to_string(other_user.id)
end
test "following without reblogs" do
follower = insert(:user)
followed = insert(:user)
other_user = insert(:user)
conn =
build_conn()
|> assign(:user, follower)
|> post("/api/v1/accounts/#{followed.id}/follow?reblogs=false")
assert %{"showing_reblogs" => false} = json_response(conn, 200)
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "hey"})
{:ok, reblog, _} = CommonAPI.repeat(activity.id, followed)
conn =
build_conn()
|> assign(:user, User.get_cached_by_id(follower.id))
|> get("/api/v1/timelines/home")
assert [] == json_response(conn, 200)
conn =
build_conn()
|> assign(:user, follower)
|> post("/api/v1/accounts/#{followed.id}/follow?reblogs=true")
assert %{"showing_reblogs" => true} = json_response(conn, 200)
conn =
build_conn()
|> assign(:user, User.get_cached_by_id(follower.id))
|> get("/api/v1/timelines/home")
expected_activity_id = reblog.id
assert [%{"id" => ^expected_activity_id}] = json_response(conn, 200)
end
test "following / unfollowing errors" do
user = insert(:user)
conn =
build_conn()
|> assign(:user, user)
# self follow
conn_res = post(conn, "/api/v1/accounts/#{user.id}/follow")
assert %{"error" => "Record not found"} = json_response(conn_res, 404)
# self unfollow
user = User.get_cached_by_id(user.id)
conn_res = post(conn, "/api/v1/accounts/#{user.id}/unfollow")
assert %{"error" => "Record not found"} = json_response(conn_res, 404)
# self follow via uri
user = User.get_cached_by_id(user.id)
conn_res = post(conn, "/api/v1/follows", %{"uri" => user.nickname})
assert %{"error" => "Record not found"} = json_response(conn_res, 404)
# follow non existing user
conn_res = post(conn, "/api/v1/accounts/doesntexist/follow")
assert %{"error" => "Record not found"} = json_response(conn_res, 404)
# follow non existing user via uri
conn_res = post(conn, "/api/v1/follows", %{"uri" => "doesntexist"})
assert %{"error" => "Record not found"} = json_response(conn_res, 404)
# unfollow non existing user
conn_res = post(conn, "/api/v1/accounts/doesntexist/unfollow")
assert %{"error" => "Record not found"} = json_response(conn_res, 404)
end
describe "mute/unmute" do
test "with notifications", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/accounts/#{other_user.id}/mute")
response = json_response(conn, 200)
assert %{"id" => _id, "muting" => true, "muting_notifications" => true} = response
user = User.get_cached_by_id(user.id)
conn =
build_conn()
|> assign(:user, user)
|> post("/api/v1/accounts/#{other_user.id}/unmute")
response = json_response(conn, 200)
assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response
end
test "without notifications", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/accounts/#{other_user.id}/mute", %{"notifications" => "false"})
response = json_response(conn, 200)
assert %{"id" => _id, "muting" => true, "muting_notifications" => false} = response
user = User.get_cached_by_id(user.id)
conn =
build_conn()
|> assign(:user, user)
|> post("/api/v1/accounts/#{other_user.id}/unmute")
response = json_response(conn, 200)
assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response
end
end
test "subscribing / unsubscribing to a user", %{conn: conn} do
user = insert(:user)
subscription_target = insert(:user)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/pleroma/accounts/#{subscription_target.id}/subscribe")
assert %{"id" => _id, "subscribing" => true} = json_response(conn, 200)
conn =
build_conn()
|> assign(:user, user)
|> post("/api/v1/pleroma/accounts/#{subscription_target.id}/unsubscribe")
assert %{"id" => _id, "subscribing" => false} = json_response(conn, 200)
end
test "getting a list of mutes", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, user} = User.mute(user, other_user)
conn =
conn
|> assign(:user, user)
|> get("/api/v1/mutes")
other_user_id = to_string(other_user.id)
assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
end
test "blocking / unblocking a user", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/accounts/#{other_user.id}/block")
assert %{"id" => _id, "blocking" => true} = json_response(conn, 200)
user = User.get_cached_by_id(user.id)
conn =
build_conn()
|> assign(:user, user)
|> post("/api/v1/accounts/#{other_user.id}/unblock")
assert %{"id" => _id, "blocking" => false} = json_response(conn, 200)
end
test "getting a list of blocks", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, user} = User.block(user, other_user)
conn =
conn
|> assign(:user, user)
|> get("/api/v1/blocks")
other_user_id = to_string(other_user.id)
assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
end
test "blocking / unblocking a domain", %{conn: conn} do
user = insert(:user)
other_user = insert(:user, %{ap_id: "https://dogwhistle.zone/@pundit"})
conn =
conn
|> assign(:user, user)
|> post("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
assert %{} = json_response(conn, 200)
user = User.get_cached_by_ap_id(user.ap_id)
assert User.blocks?(user, other_user)
conn =
build_conn()
|> assign(:user, user)
|> delete("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
assert %{} = json_response(conn, 200)
user = User.get_cached_by_ap_id(user.ap_id)
refute User.blocks?(user, other_user)
end
test "getting a list of domain blocks", %{conn: conn} do
user = insert(:user)
{:ok, user} = User.block_domain(user, "bad.site")
{:ok, user} = User.block_domain(user, "even.worse.site")
conn =
conn
|> assign(:user, user)
|> get("/api/v1/domain_blocks")
domain_blocks = json_response(conn, 200)
assert "bad.site" in domain_blocks
assert "even.worse.site" in domain_blocks
end
test "unimplemented follow_requests, blocks, domain blocks" do
user = insert(:user)
["blocks", "domain_blocks", "follow_requests"]
|> Enum.each(fn endpoint ->
conn =
build_conn()
|> assign(:user, user)
|> get("/api/v1/#{endpoint}")
assert [] = json_response(conn, 200)
end)
end
test "returns the favorites of a user", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, _} = CommonAPI.post(other_user, %{"status" => "bla"})
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "traps are happy"})
{:ok, _, _} = CommonAPI.favorite(activity.id, user)
first_conn =
conn
|> assign(:user, user)
|> get("/api/v1/favourites")
assert [status] = json_response(first_conn, 200)
assert status["id"] == to_string(activity.id)
assert [{"link", _link_header}] =
Enum.filter(first_conn.resp_headers, fn element -> match?({"link", _}, element) end)
# Honours query params
{:ok, second_activity} =
CommonAPI.post(other_user, %{
"status" =>
"Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful."
})
{:ok, _, _} = CommonAPI.favorite(second_activity.id, user)
last_like = status["id"]
second_conn =
conn
|> assign(:user, user)
|> get("/api/v1/favourites?since_id=#{last_like}")
assert [second_status] = json_response(second_conn, 200)
assert second_status["id"] == to_string(second_activity.id)
third_conn =
conn
|> assign(:user, user)
|> get("/api/v1/favourites?limit=0")
assert [] = json_response(third_conn, 200)
end
describe "getting favorites timeline of specified user" do
setup do
[current_user, user] = insert_pair(:user, %{info: %{hide_favorites: false}})
[current_user: current_user, user: user]
end
test "returns list of statuses favorited by specified user", %{
conn: conn,
current_user: current_user,
user: user
} do
[activity | _] = insert_pair(:note_activity)
CommonAPI.favorite(activity.id, user)
response =
conn
|> assign(:user, current_user)
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|> json_response(:ok)
[like] = response
assert length(response) == 1
assert like["id"] == activity.id
end
test "returns favorites for specified user_id when user is not logged in", %{
conn: conn,
user: user
} do
activity = insert(:note_activity)
CommonAPI.favorite(activity.id, user)
response =
conn
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|> json_response(:ok)
assert length(response) == 1
end
test "returns favorited DM only when user is logged in and he is one of recipients", %{
conn: conn,
current_user: current_user,
user: user
} do
{:ok, direct} =
CommonAPI.post(current_user, %{
"status" => "Hi @#{user.nickname}!",
"visibility" => "direct"
})
CommonAPI.favorite(direct.id, user)
response =
conn
|> assign(:user, current_user)
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|> json_response(:ok)
assert length(response) == 1
anonymous_response =
conn
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|> json_response(:ok)
assert Enum.empty?(anonymous_response)
end
test "does not return others' favorited DM when user is not one of recipients", %{
conn: conn,
current_user: current_user,
user: user
} do
user_two = insert(:user)
{:ok, direct} =
CommonAPI.post(user_two, %{
"status" => "Hi @#{user.nickname}!",
"visibility" => "direct"
})
CommonAPI.favorite(direct.id, user)
response =
conn
|> assign(:user, current_user)
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|> json_response(:ok)
assert Enum.empty?(response)
end
test "paginates favorites using since_id and max_id", %{
conn: conn,
current_user: current_user,
user: user
} do
activities = insert_list(10, :note_activity)
Enum.each(activities, fn activity ->
CommonAPI.favorite(activity.id, user)
end)
third_activity = Enum.at(activities, 2)
seventh_activity = Enum.at(activities, 6)
response =
conn
|> assign(:user, current_user)
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{
since_id: third_activity.id,
max_id: seventh_activity.id
})
|> json_response(:ok)
assert length(response) == 3
refute third_activity in response
refute seventh_activity in response
end
test "limits favorites using limit parameter", %{
conn: conn,
current_user: current_user,
user: user
} do
7
|> insert_list(:note_activity)
|> Enum.each(fn activity ->
CommonAPI.favorite(activity.id, user)
end)
response =
conn
|> assign(:user, current_user)
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{limit: "3"})
|> json_response(:ok)
assert length(response) == 3
end
test "returns empty response when user does not have any favorited statuses", %{
conn: conn,
current_user: current_user,
user: user
} do
response =
conn
|> assign(:user, current_user)
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|> json_response(:ok)
assert Enum.empty?(response)
end
test "returns 404 error when specified user is not exist", %{conn: conn} do
conn = get(conn, "/api/v1/pleroma/accounts/test/favourites")
assert json_response(conn, 404) == %{"error" => "Record not found"}
end
test "returns 403 error when user has hidden own favorites", %{
conn: conn,
current_user: current_user
} do
user = insert(:user, %{info: %{hide_favorites: true}})
activity = insert(:note_activity)
CommonAPI.favorite(activity.id, user)
conn =
conn
|> assign(:user, current_user)
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
assert json_response(conn, 403) == %{"error" => "Can't get favorites"}
end
test "hides favorites for new users by default", %{conn: conn, current_user: current_user} do
user = insert(:user)
activity = insert(:note_activity)
CommonAPI.favorite(activity.id, user)
conn =
conn
|> assign(:user, current_user)
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
assert user.info.hide_favorites
assert json_response(conn, 403) == %{"error" => "Can't get favorites"}
end
end
test "get instance information", %{conn: conn} do
conn = get(conn, "/api/v1/instance")
assert result = json_response(conn, 200)
email = Pleroma.Config.get([:instance, :email])
# Note: not checking for "max_toot_chars" since it's optional
assert %{
"uri" => _,
"title" => _,
"description" => _,
"version" => _,
"email" => from_config_email,
"urls" => %{
"streaming_api" => _
},
"stats" => _,
"thumbnail" => _,
"languages" => _,
"registrations" => _,
"poll_limits" => _
} = result
assert email == from_config_email
end
test "get instance stats", %{conn: conn} do
user = insert(:user, %{local: true})
user2 = insert(:user, %{local: true})
{:ok, _user2} = User.deactivate(user2, !user2.info.deactivated)
insert(:user, %{local: false, nickname: "u@peer1.com"})
insert(:user, %{local: false, nickname: "u@peer2.com"})
{:ok, _} = TwitterAPI.create_status(user, %{"status" => "cofe"})
# Stats should count users with missing or nil `info.deactivated` value
user = User.get_cached_by_id(user.id)
info_change = Changeset.change(user.info, %{deactivated: nil})
{:ok, _user} =
user
|> Changeset.change()
|> Changeset.put_embed(:info, info_change)
|> User.update_and_set_cache()
Pleroma.Stats.update_stats()
conn = get(conn, "/api/v1/instance")
assert result = json_response(conn, 200)
stats = result["stats"]
assert stats
assert stats["user_count"] == 1
assert stats["status_count"] == 1
assert stats["domain_count"] == 2
end
test "get peers", %{conn: conn} do
insert(:user, %{local: false, nickname: "u@peer1.com"})
insert(:user, %{local: false, nickname: "u@peer2.com"})
Pleroma.Stats.update_stats()
conn = get(conn, "/api/v1/instance/peers")
assert result = json_response(conn, 200)
assert ["peer1.com", "peer2.com"] == Enum.sort(result)
end
test "put settings", %{conn: conn} do
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> put("/api/web/settings", %{"data" => %{"programming" => "socks"}})
assert _result = json_response(conn, 200)
user = User.get_cached_by_ap_id(user.ap_id)
assert user.info.settings == %{"programming" => "socks"}
end
describe "pinned statuses" do
setup do
Pleroma.Config.put([:instance, :max_pinned_statuses], 1)
user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"})
[user: user, activity: activity]
end
test "returns pinned statuses", %{conn: conn, user: user, activity: activity} do
{:ok, _} = CommonAPI.pin(activity.id, user)
result =
conn
|> assign(:user, user)
|> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
|> json_response(200)
id_str = to_string(activity.id)
assert [%{"id" => ^id_str, "pinned" => true}] = result
end
test "pin status", %{conn: conn, user: user, activity: activity} do
id_str = to_string(activity.id)
assert %{"id" => ^id_str, "pinned" => true} =
conn
|> assign(:user, user)
|> post("/api/v1/statuses/#{activity.id}/pin")
|> json_response(200)
assert [%{"id" => ^id_str, "pinned" => true}] =
conn
|> assign(:user, user)
|> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
|> json_response(200)
end
test "unpin status", %{conn: conn, user: user, activity: activity} do
{:ok, _} = CommonAPI.pin(activity.id, user)
id_str = to_string(activity.id)
user = refresh_record(user)
assert %{"id" => ^id_str, "pinned" => false} =
conn
|> assign(:user, user)
|> post("/api/v1/statuses/#{activity.id}/unpin")
|> json_response(200)
assert [] =
conn
|> assign(:user, user)
|> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
|> json_response(200)
end
test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do
{:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
id_str_one = to_string(activity_one.id)
assert %{"id" => ^id_str_one, "pinned" => true} =
conn
|> assign(:user, user)
|> post("/api/v1/statuses/#{id_str_one}/pin")
|> json_response(200)
user = refresh_record(user)
assert %{"error" => "You have already pinned the maximum number of statuses"} =
conn
|> assign(:user, user)
|> post("/api/v1/statuses/#{activity_two.id}/pin")
|> json_response(400)
end
end
describe "cards" do
setup do
Pleroma.Config.put([:rich_media, :enabled], true)
on_exit(fn ->
Pleroma.Config.put([:rich_media, :enabled], false)
end)
user = insert(:user)
%{user: user}
end
test "returns rich-media card", %{conn: conn, user: user} do
{:ok, activity} = CommonAPI.post(user, %{"status" => "https://example.com/ogp"})
card_data = %{
"image" => "http://ia.media-imdb.com/images/rock.jpg",
"provider_name" => "www.imdb.com",
"provider_url" => "http://www.imdb.com",
"title" => "The Rock",
"type" => "link",
"url" => "http://www.imdb.com/title/tt0117500/",
"description" =>
"Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
"pleroma" => %{
"opengraph" => %{
"image" => "http://ia.media-imdb.com/images/rock.jpg",
"title" => "The Rock",
"type" => "video.movie",
"url" => "http://www.imdb.com/title/tt0117500/",
"description" =>
"Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer."
}
}
}
response =
conn
|> get("/api/v1/statuses/#{activity.id}/card")
|> json_response(200)
assert response == card_data
# works with private posts
{:ok, activity} =
CommonAPI.post(user, %{"status" => "https://example.com/ogp", "visibility" => "direct"})
response_two =
conn
|> assign(:user, user)
|> get("/api/v1/statuses/#{activity.id}/card")
|> json_response(200)
assert response_two == card_data
end
test "replaces missing description with an empty string", %{conn: conn, user: user} do
{:ok, activity} =
CommonAPI.post(user, %{"status" => "https://example.com/ogp-missing-data"})
response =
conn
|> get("/api/v1/statuses/#{activity.id}/card")
|> json_response(:ok)
assert response == %{
"type" => "link",
"title" => "Pleroma",
"description" => "",
"image" => nil,
"provider_name" => "pleroma.social",
"provider_url" => "https://pleroma.social",
"url" => "https://pleroma.social/",
"pleroma" => %{
"opengraph" => %{
"title" => "Pleroma",
"type" => "website",
"url" => "https://pleroma.social/"
}
}
}
end
end
test "bookmarks" do
user = insert(:user)
for_user = insert(:user)
{:ok, activity1} =
CommonAPI.post(user, %{
"status" => "heweoo?"
})
{:ok, activity2} =
CommonAPI.post(user, %{
"status" => "heweoo!"
})
response1 =
build_conn()
|> assign(:user, for_user)
|> post("/api/v1/statuses/#{activity1.id}/bookmark")
assert json_response(response1, 200)["bookmarked"] == true
response2 =
build_conn()
|> assign(:user, for_user)
|> post("/api/v1/statuses/#{activity2.id}/bookmark")
assert json_response(response2, 200)["bookmarked"] == true
bookmarks =
build_conn()
|> assign(:user, for_user)
|> get("/api/v1/bookmarks")
assert [json_response(response2, 200), json_response(response1, 200)] ==
json_response(bookmarks, 200)
response1 =
build_conn()
|> assign(:user, for_user)
|> post("/api/v1/statuses/#{activity1.id}/unbookmark")
assert json_response(response1, 200)["bookmarked"] == false
bookmarks =
build_conn()
|> assign(:user, for_user)
|> get("/api/v1/bookmarks")
assert [json_response(response2, 200)] == json_response(bookmarks, 200)
end
describe "conversation muting" do
setup do
user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "HIE"})
[user: user, activity: activity]
end
test "mute conversation", %{conn: conn, user: user, activity: activity} do
id_str = to_string(activity.id)
assert %{"id" => ^id_str, "muted" => true} =
conn
|> assign(:user, user)
|> post("/api/v1/statuses/#{activity.id}/mute")
|> json_response(200)
end
test "unmute conversation", %{conn: conn, user: user, activity: activity} do
{:ok, _} = CommonAPI.add_mute(user, activity)
id_str = to_string(activity.id)
user = refresh_record(user)
assert %{"id" => ^id_str, "muted" => false} =
conn
|> assign(:user, user)
|> post("/api/v1/statuses/#{activity.id}/unmute")
|> json_response(200)
end
end
describe "reports" do
setup do
reporter = insert(:user)
target_user = insert(:user)
{:ok, activity} = CommonAPI.post(target_user, %{"status" => "foobar"})
[reporter: reporter, target_user: target_user, activity: activity]
end
test "submit a basic report", %{conn: conn, reporter: reporter, target_user: target_user} do
assert %{"action_taken" => false, "id" => _} =
conn
|> assign(:user, reporter)
|> post("/api/v1/reports", %{"account_id" => target_user.id})
|> json_response(200)
end
test "submit a report with statuses and comment", %{
conn: conn,
reporter: reporter,
target_user: target_user,
activity: activity
} do
assert %{"action_taken" => false, "id" => _} =
conn
|> assign(:user, reporter)
|> post("/api/v1/reports", %{
"account_id" => target_user.id,
"status_ids" => [activity.id],
"comment" => "bad status!"
})
|> json_response(200)
end
test "account_id is required", %{
conn: conn,
reporter: reporter,
activity: activity
} do
assert %{"error" => "Valid `account_id` required"} =
conn
|> assign(:user, reporter)
|> post("/api/v1/reports", %{"status_ids" => [activity.id]})
|> json_response(400)
end
test "comment must be up to the size specified in the config", %{
conn: conn,
reporter: reporter,
target_user: target_user
} do
max_size = Pleroma.Config.get([:instance, :max_report_comment_size], 1000)
comment = String.pad_trailing("a", max_size + 1, "a")
error = %{"error" => "Comment must be up to #{max_size} characters"}
assert ^error =
conn
|> assign(:user, reporter)
|> post("/api/v1/reports", %{"account_id" => target_user.id, "comment" => comment})
|> json_response(400)
end
end
describe "link headers" do
test "preserves parameters in link headers", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, activity1} =
CommonAPI.post(other_user, %{
"status" => "hi @#{user.nickname}",
"visibility" => "public"
})
{:ok, activity2} =
CommonAPI.post(other_user, %{
"status" => "hi @#{user.nickname}",
"visibility" => "public"
})
notification1 = Repo.get_by(Notification, activity_id: activity1.id)
notification2 = Repo.get_by(Notification, activity_id: activity2.id)
conn =
conn
|> assign(:user, user)
|> get("/api/v1/notifications", %{media_only: true})
assert [link_header] = get_resp_header(conn, "link")
assert link_header =~ ~r/media_only=true/
assert link_header =~ ~r/min_id=#{notification2.id}/
assert link_header =~ ~r/max_id=#{notification1.id}/
end
end
test "accounts fetches correct account for nicknames beginning with numbers", %{conn: conn} do
# Need to set an old-style integer ID to reproduce the problem
# (these are no longer assigned to new accounts but were preserved
# for existing accounts during the migration to flakeIDs)
user_one = insert(:user, %{id: 1212})
user_two = insert(:user, %{nickname: "#{user_one.id}garbage"})
resp_one =
conn
|> get("/api/v1/accounts/#{user_one.id}")
resp_two =
conn
|> get("/api/v1/accounts/#{user_two.nickname}")
resp_three =
conn
|> get("/api/v1/accounts/#{user_two.id}")
acc_one = json_response(resp_one, 200)
acc_two = json_response(resp_two, 200)
acc_three = json_response(resp_three, 200)
refute acc_one == acc_two
assert acc_two == acc_three
end
describe "custom emoji" do
test "with tags", %{conn: conn} do
[emoji | _body] =
conn
|> get("/api/v1/custom_emojis")
|> json_response(200)
assert Map.has_key?(emoji, "shortcode")
assert Map.has_key?(emoji, "static_url")
assert Map.has_key?(emoji, "tags")
assert is_list(emoji["tags"])
assert Map.has_key?(emoji, "category")
assert Map.has_key?(emoji, "url")
assert Map.has_key?(emoji, "visible_in_picker")
end
end
describe "index/2 redirections" do
setup %{conn: conn} do
session_opts = [
store: :cookie,
key: "_test",
signing_salt: "cooldude"
]
conn =
conn
|> Plug.Session.call(Plug.Session.init(session_opts))
|> fetch_session()
test_path = "/web/statuses/test"
%{conn: conn, path: test_path}
end
test "redirects not logged-in users to the login page", %{conn: conn, path: path} do
conn = get(conn, path)
assert conn.status == 302
assert redirected_to(conn) == "/web/login"
end
test "does not redirect logged in users to the login page", %{conn: conn, path: path} do
token = insert(:oauth_token)
conn =
conn
|> assign(:user, token.user)
|> put_session(:oauth_token, token.token)
|> get(path)
assert conn.status == 200
end
test "saves referer path to session", %{conn: conn, path: path} do
conn = get(conn, path)
return_to = Plug.Conn.get_session(conn, :return_to)
assert return_to == path
end
test "redirects to the saved path after log in", %{conn: conn, path: path} do
app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".")
auth = insert(:oauth_authorization, app: app)
conn =
conn
|> put_session(:return_to, path)
|> get("/web/login", %{code: auth.token})
assert conn.status == 302
assert redirected_to(conn) == path
end
test "redirects to the getting-started page when referer is not present", %{conn: conn} do
app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".")
auth = insert(:oauth_authorization, app: app)
conn = get(conn, "/web/login", %{code: auth.token})
assert conn.status == 302
assert redirected_to(conn) == "/web/getting-started"
end
end
describe "scheduled activities" do
test "creates a scheduled activity", %{conn: conn} do
user = insert(:user)
scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/statuses", %{
"status" => "scheduled",
"scheduled_at" => scheduled_at
})
assert %{"scheduled_at" => expected_scheduled_at} = json_response(conn, 200)
assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(scheduled_at)
assert [] == Repo.all(Activity)
end
test "creates a scheduled activity with a media attachment", %{conn: conn} do
user = insert(:user)
scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
file = %Plug.Upload{
content_type: "image/jpg",
path: Path.absname("test/fixtures/image.jpg"),
filename: "an_image.jpg"
}
{:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/statuses", %{
"media_ids" => [to_string(upload.id)],
"status" => "scheduled",
"scheduled_at" => scheduled_at
})
assert %{"media_attachments" => [media_attachment]} = json_response(conn, 200)
assert %{"type" => "image"} = media_attachment
end
test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now",
%{conn: conn} do
user = insert(:user)
scheduled_at =
NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/statuses", %{
"status" => "not scheduled",
"scheduled_at" => scheduled_at
})
assert %{"content" => "not scheduled"} = json_response(conn, 200)
assert [] == Repo.all(ScheduledActivity)
end
test "returns error when daily user limit is exceeded", %{conn: conn} do
user = insert(:user)
today =
NaiveDateTime.utc_now()
|> NaiveDateTime.add(:timer.minutes(6), :millisecond)
|> NaiveDateTime.to_iso8601()
attrs = %{params: %{}, scheduled_at: today}
{:ok, _} = ScheduledActivity.create(user, attrs)
{:ok, _} = ScheduledActivity.create(user, attrs)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today})
assert %{"error" => "daily limit exceeded"} == json_response(conn, 422)
end
test "returns error when total user limit is exceeded", %{conn: conn} do
user = insert(:user)
today =
NaiveDateTime.utc_now()
|> NaiveDateTime.add(:timer.minutes(6), :millisecond)
|> NaiveDateTime.to_iso8601()
tomorrow =
NaiveDateTime.utc_now()
|> NaiveDateTime.add(:timer.hours(36), :millisecond)
|> NaiveDateTime.to_iso8601()
attrs = %{params: %{}, scheduled_at: today}
{:ok, _} = ScheduledActivity.create(user, attrs)
{:ok, _} = ScheduledActivity.create(user, attrs)
{:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow})
conn =
conn
|> assign(:user, user)
|> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow})
assert %{"error" => "total limit exceeded"} == json_response(conn, 422)
end
test "shows scheduled activities", %{conn: conn} do
user = insert(:user)
scheduled_activity_id1 = insert(:scheduled_activity, user: user).id |> to_string()
scheduled_activity_id2 = insert(:scheduled_activity, user: user).id |> to_string()
scheduled_activity_id3 = insert(:scheduled_activity, user: user).id |> to_string()
scheduled_activity_id4 = insert(:scheduled_activity, user: user).id |> to_string()
conn =
conn
|> assign(:user, user)
# min_id
conn_res =
conn
|> get("/api/v1/scheduled_statuses?limit=2&min_id=#{scheduled_activity_id1}")
result = json_response(conn_res, 200)
assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
# since_id
conn_res =
conn
|> get("/api/v1/scheduled_statuses?limit=2&since_id=#{scheduled_activity_id1}")
result = json_response(conn_res, 200)
assert [%{"id" => ^scheduled_activity_id4}, %{"id" => ^scheduled_activity_id3}] = result
# max_id
conn_res =
conn
|> get("/api/v1/scheduled_statuses?limit=2&max_id=#{scheduled_activity_id4}")
result = json_response(conn_res, 200)
assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result
end
test "shows a scheduled activity", %{conn: conn} do
user = insert(:user)
scheduled_activity = insert(:scheduled_activity, user: user)
res_conn =
conn
|> assign(:user, user)
|> get("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
assert %{"id" => scheduled_activity_id} = json_response(res_conn, 200)
assert scheduled_activity_id == scheduled_activity.id |> to_string()
res_conn =
conn
|> assign(:user, user)
|> get("/api/v1/scheduled_statuses/404")
assert %{"error" => "Record not found"} = json_response(res_conn, 404)
end
test "updates a scheduled activity", %{conn: conn} do
user = insert(:user)
scheduled_activity = insert(:scheduled_activity, user: user)
new_scheduled_at =
NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
res_conn =
conn
|> assign(:user, user)
|> put("/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{
scheduled_at: new_scheduled_at
})
assert %{"scheduled_at" => expected_scheduled_at} = json_response(res_conn, 200)
assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(new_scheduled_at)
res_conn =
conn
|> assign(:user, user)
|> put("/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at})
assert %{"error" => "Record not found"} = json_response(res_conn, 404)
end
test "deletes a scheduled activity", %{conn: conn} do
user = insert(:user)
scheduled_activity = insert(:scheduled_activity, user: user)
res_conn =
conn
|> assign(:user, user)
|> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
assert %{} = json_response(res_conn, 200)
assert nil == Repo.get(ScheduledActivity, scheduled_activity.id)
res_conn =
conn
|> assign(:user, user)
|> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}")
assert %{"error" => "Record not found"} = json_response(res_conn, 404)
end
end
test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do
user1 = insert(:user)
user2 = insert(:user)
user3 = insert(:user)
{:ok, replied_to} = TwitterAPI.create_status(user1, %{"status" => "cofe"})
# Reply to status from another user
conn1 =
conn
|> assign(:user, user2)
|> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
assert %{"content" => "xD", "id" => id} = json_response(conn1, 200)
activity = Activity.get_by_id_with_object(id)
assert Object.normalize(activity).data["inReplyTo"] == Object.normalize(replied_to).data["id"]
assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
# Reblog from the third user
conn2 =
conn
|> assign(:user, user3)
|> post("/api/v1/statuses/#{activity.id}/reblog")
assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} =
json_response(conn2, 200)
assert to_string(activity.id) == id
# Getting third user status
conn3 =
conn
|> assign(:user, user3)
|> get("api/v1/timelines/home")
[reblogged_activity] = json_response(conn3, 200)
assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id
replied_to_user = User.get_by_ap_id(replied_to.data["actor"])
assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id
end
describe "create account by app" do
test "Account registration via Application", %{conn: conn} do
conn =
conn
|> post("/api/v1/apps", %{
client_name: "client_name",
redirect_uris: "urn:ietf:wg:oauth:2.0:oob",
scopes: "read, write, follow"
})
%{
"client_id" => client_id,
"client_secret" => client_secret,
"id" => _,
"name" => "client_name",
"redirect_uri" => "urn:ietf:wg:oauth:2.0:oob",
"vapid_key" => _,
"website" => nil
} = json_response(conn, 200)
conn =
conn
|> post("/oauth/token", %{
grant_type: "client_credentials",
client_id: client_id,
client_secret: client_secret
})
assert %{"access_token" => token, "refresh_token" => refresh, "scope" => scope} =
json_response(conn, 200)
assert token
token_from_db = Repo.get_by(Token, token: token)
assert token_from_db
assert refresh
assert scope == "read write follow"
conn =
build_conn()
|> put_req_header("authorization", "Bearer " <> token)
|> post("/api/v1/accounts", %{
username: "lain",
email: "lain@example.org",
password: "PlzDontHackLain",
agreement: true
})
%{
"access_token" => token,
"created_at" => _created_at,
"scope" => _scope,
"token_type" => "Bearer"
} = json_response(conn, 200)
token_from_db = Repo.get_by(Token, token: token)
assert token_from_db
token_from_db = Repo.preload(token_from_db, :user)
assert token_from_db.user
assert token_from_db.user.info.confirmation_pending
end
test "rate limit", %{conn: conn} do
app_token = insert(:oauth_token, user: nil)
conn =
put_req_header(conn, "authorization", "Bearer " <> app_token.token)
|> Map.put(:remote_ip, {15, 15, 15, 15})
for i <- 1..5 do
conn =
conn
|> post("/api/v1/accounts", %{
username: "#{i}lain",
email: "#{i}lain@example.org",
password: "PlzDontHackLain",
agreement: true
})
%{
"access_token" => token,
"created_at" => _created_at,
"scope" => _scope,
"token_type" => "Bearer"
} = json_response(conn, 200)
token_from_db = Repo.get_by(Token, token: token)
assert token_from_db
token_from_db = Repo.preload(token_from_db, :user)
assert token_from_db.user
assert token_from_db.user.info.confirmation_pending
end
conn =
conn
|> post("/api/v1/accounts", %{
username: "6lain",
email: "6lain@example.org",
password: "PlzDontHackLain",
agreement: true
})
assert json_response(conn, :too_many_requests) == %{"error" => "Throttled"}
end
end
describe "GET /api/v1/polls/:id" do
test "returns poll entity for object id", %{conn: conn} do
user = insert(:user)
{:ok, activity} =
CommonAPI.post(user, %{
"status" => "Pleroma does",
"poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20}
})
object = Object.normalize(activity)
conn =
conn
|> assign(:user, user)
|> get("/api/v1/polls/#{object.id}")
response = json_response(conn, 200)
id = object.id
assert %{"id" => ^id, "expired" => false, "multiple" => false} = response
end
test "does not expose polls for private statuses", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} =
CommonAPI.post(user, %{
"status" => "Pleroma does",
"poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20},
"visibility" => "private"
})
object = Object.normalize(activity)
conn =
conn
|> assign(:user, other_user)
|> get("/api/v1/polls/#{object.id}")
assert json_response(conn, 404)
end
end
describe "POST /api/v1/polls/:id/votes" do
test "votes are added to the poll", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} =
CommonAPI.post(user, %{
"status" => "A very delicious sandwich",
"poll" => %{
"options" => ["Lettuce", "Grilled Bacon", "Tomato"],
"expires_in" => 20,
"multiple" => true
}
})
object = Object.normalize(activity)
conn =
conn
|> assign(:user, other_user)
|> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1, 2]})
assert json_response(conn, 200)
object = Object.get_by_id(object.id)
assert Enum.all?(object.data["anyOf"], fn %{"replies" => %{"totalItems" => total_items}} ->
total_items == 1
end)
end
test "author can't vote", %{conn: conn} do
user = insert(:user)
{:ok, activity} =
CommonAPI.post(user, %{
"status" => "Am I cute?",
"poll" => %{"options" => ["Yes", "No"], "expires_in" => 20}
})
object = Object.normalize(activity)
assert conn
|> assign(:user, user)
|> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [1]})
|> json_response(422) == %{"error" => "Poll's author can't vote"}
object = Object.get_by_id(object.id)
refute Enum.at(object.data["oneOf"], 1)["replies"]["totalItems"] == 1
end
test "does not allow multiple choices on a single-choice question", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, activity} =
CommonAPI.post(user, %{
"status" => "The glass is",
"poll" => %{"options" => ["half empty", "half full"], "expires_in" => 20}
})
object = Object.normalize(activity)
assert conn
|> assign(:user, other_user)
|> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1]})
|> json_response(422) == %{"error" => "Too many choices"}
object = Object.get_by_id(object.id)
refute Enum.any?(object.data["oneOf"], fn %{"replies" => %{"totalItems" => total_items}} ->
total_items == 1
end)
end
end
end
diff --git a/test/web/twitter_api/twitter_api_controller_test.exs b/test/web/twitter_api/twitter_api_controller_test.exs
index 7ec0e101d..de6177575 100644
--- a/test/web/twitter_api/twitter_api_controller_test.exs
+++ b/test/web/twitter_api/twitter_api_controller_test.exs
@@ -1,2125 +1,2157 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.TwitterAPI.ControllerTest do
use Pleroma.Web.ConnCase
alias Comeonin.Pbkdf2
alias Ecto.Changeset
alias Pleroma.Activity
alias Pleroma.Builders.ActivityBuilder
alias Pleroma.Builders.UserBuilder
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.TwitterAPI.ActivityView
alias Pleroma.Web.TwitterAPI.Controller
alias Pleroma.Web.TwitterAPI.NotificationView
alias Pleroma.Web.TwitterAPI.TwitterAPI
alias Pleroma.Web.TwitterAPI.UserView
import Mock
import Pleroma.Factory
import Swoosh.TestAssertions
@banner "data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7"
describe "POST /api/account/update_profile_banner" do
test "it updates the banner", %{conn: conn} do
user = insert(:user)
conn
|> assign(:user, user)
|> post(authenticated_twitter_api__path(conn, :update_banner), %{"banner" => @banner})
|> json_response(200)
user = refresh_record(user)
assert user.info.banner["type"] == "Image"
end
test "profile banner can be reset", %{conn: conn} do
user = insert(:user)
conn
|> assign(:user, user)
|> post(authenticated_twitter_api__path(conn, :update_banner), %{"banner" => ""})
|> json_response(200)
user = refresh_record(user)
assert user.info.banner == %{}
end
end
describe "POST /api/qvitter/update_background_image" do
test "it updates the background", %{conn: conn} do
user = insert(:user)
conn
|> assign(:user, user)
|> post(authenticated_twitter_api__path(conn, :update_background), %{"img" => @banner})
|> json_response(200)
user = refresh_record(user)
assert user.info.background["type"] == "Image"
end
test "background can be reset", %{conn: conn} do
user = insert(:user)
conn
|> assign(:user, user)
|> post(authenticated_twitter_api__path(conn, :update_background), %{"img" => ""})
|> json_response(200)
user = refresh_record(user)
assert user.info.background == %{}
end
end
describe "POST /api/account/verify_credentials" do
setup [:valid_user]
test "without valid credentials", %{conn: conn} do
conn = post(conn, "/api/account/verify_credentials.json")
assert json_response(conn, 403) == %{"error" => "Invalid credentials."}
end
test "with credentials", %{conn: conn, user: user} do
response =
conn
|> with_credentials(user.nickname, "test")
|> post("/api/account/verify_credentials.json")
|> json_response(200)
assert response ==
UserView.render("show.json", %{user: user, token: response["token"], for: user})
end
end
describe "POST /statuses/update.json" do
setup [:valid_user]
test "without valid credentials", %{conn: conn} do
conn = post(conn, "/api/statuses/update.json")
assert json_response(conn, 403) == %{"error" => "Invalid credentials."}
end
test "with credentials", %{conn: conn, user: user} do
conn_with_creds = conn |> with_credentials(user.nickname, "test")
request_path = "/api/statuses/update.json"
error_response = %{
"request" => request_path,
"error" => "Client must provide a 'status' parameter with a value."
}
conn =
conn_with_creds
|> post(request_path)
assert json_response(conn, 400) == error_response
conn =
conn_with_creds
|> post(request_path, %{status: ""})
assert json_response(conn, 400) == error_response
conn =
conn_with_creds
|> post(request_path, %{status: " "})
assert json_response(conn, 400) == error_response
# we post with visibility private in order to avoid triggering relay
conn =
conn_with_creds
|> post(request_path, %{status: "Nice meme.", visibility: "private"})
assert json_response(conn, 200) ==
ActivityView.render("activity.json", %{
activity: Repo.one(Activity),
user: user,
for: user
})
end
end
describe "GET /statuses/public_timeline.json" do
setup [:valid_user]
test "returns statuses", %{conn: conn} do
user = insert(:user)
activities = ActivityBuilder.insert_list(30, %{}, %{user: user})
ActivityBuilder.insert_list(10, %{}, %{user: user})
since_id = List.last(activities).id
conn =
conn
|> get("/api/statuses/public_timeline.json", %{since_id: since_id})
response = json_response(conn, 200)
assert length(response) == 10
end
test "returns 403 to unauthenticated request when the instance is not public", %{conn: conn} do
Pleroma.Config.put([:instance, :public], false)
conn
|> get("/api/statuses/public_timeline.json")
|> json_response(403)
Pleroma.Config.put([:instance, :public], true)
end
test "returns 200 to authenticated request when the instance is not public",
%{conn: conn, user: user} do
Pleroma.Config.put([:instance, :public], false)
conn
|> with_credentials(user.nickname, "test")
|> get("/api/statuses/public_timeline.json")
|> json_response(200)
Pleroma.Config.put([:instance, :public], true)
end
test "returns 200 to unauthenticated request when the instance is public", %{conn: conn} do
conn
|> get("/api/statuses/public_timeline.json")
|> json_response(200)
end
test "returns 200 to authenticated request when the instance is public",
%{conn: conn, user: user} do
conn
|> with_credentials(user.nickname, "test")
|> get("/api/statuses/public_timeline.json")
|> json_response(200)
end
test_with_mock "treats user as unauthenticated if `assigns[:token]` is present but lacks `read` permission",
Controller,
[:passthrough],
[] do
token = insert(:oauth_token, scopes: ["write"])
build_conn()
|> put_req_header("authorization", "Bearer #{token.token}")
|> get("/api/statuses/public_timeline.json")
|> json_response(200)
assert called(Controller.public_timeline(%{assigns: %{user: nil}}, :_))
end
end
describe "GET /statuses/public_and_external_timeline.json" do
setup [:valid_user]
test "returns 403 to unauthenticated request when the instance is not public", %{conn: conn} do
Pleroma.Config.put([:instance, :public], false)
conn
|> get("/api/statuses/public_and_external_timeline.json")
|> json_response(403)
Pleroma.Config.put([:instance, :public], true)
end
test "returns 200 to authenticated request when the instance is not public",
%{conn: conn, user: user} do
Pleroma.Config.put([:instance, :public], false)
conn
|> with_credentials(user.nickname, "test")
|> get("/api/statuses/public_and_external_timeline.json")
|> json_response(200)
Pleroma.Config.put([:instance, :public], true)
end
test "returns 200 to unauthenticated request when the instance is public", %{conn: conn} do
conn
|> get("/api/statuses/public_and_external_timeline.json")
|> json_response(200)
end
test "returns 200 to authenticated request when the instance is public",
%{conn: conn, user: user} do
conn
|> with_credentials(user.nickname, "test")
|> get("/api/statuses/public_and_external_timeline.json")
|> json_response(200)
end
end
describe "GET /statuses/show/:id.json" do
test "returns one status", %{conn: conn} do
user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "Hey!"})
actor = User.get_cached_by_ap_id(activity.data["actor"])
conn =
conn
|> get("/api/statuses/show/#{activity.id}.json")
response = json_response(conn, 200)
assert response == ActivityView.render("activity.json", %{activity: activity, user: actor})
end
end
describe "GET /users/show.json" do
test "gets user with screen_name", %{conn: conn} do
user = insert(:user)
conn =
conn
|> get("/api/users/show.json", %{"screen_name" => user.nickname})
response = json_response(conn, 200)
assert response["id"] == user.id
end
test "gets user with user_id", %{conn: conn} do
user = insert(:user)
conn =
conn
|> get("/api/users/show.json", %{"user_id" => user.id})
response = json_response(conn, 200)
assert response["id"] == user.id
end
test "gets a user for a logged in user", %{conn: conn} do
user = insert(:user)
logged_in = insert(:user)
{:ok, logged_in, user, _activity} = TwitterAPI.follow(logged_in, %{"user_id" => user.id})
conn =
conn
|> with_credentials(logged_in.nickname, "test")
|> get("/api/users/show.json", %{"user_id" => user.id})
response = json_response(conn, 200)
assert response["following"] == true
end
end
describe "GET /statusnet/conversation/:id.json" do
test "returns the statuses in the conversation", %{conn: conn} do
{:ok, _user} = UserBuilder.insert()
{:ok, activity} = ActivityBuilder.insert(%{"type" => "Create", "context" => "2hu"})
{:ok, _activity_two} = ActivityBuilder.insert(%{"type" => "Create", "context" => "2hu"})
{:ok, _activity_three} = ActivityBuilder.insert(%{"type" => "Create", "context" => "3hu"})
conn =
conn
|> get("/api/statusnet/conversation/#{activity.data["context_id"]}.json")
response = json_response(conn, 200)
assert length(response) == 2
end
end
describe "GET /statuses/friends_timeline.json" do
setup [:valid_user]
test "without valid credentials", %{conn: conn} do
conn = get(conn, "/api/statuses/friends_timeline.json")
assert json_response(conn, 403) == %{"error" => "Invalid credentials."}
end
test "with credentials", %{conn: conn, user: current_user} do
user = insert(:user)
activities =
ActivityBuilder.insert_list(30, %{"to" => [User.ap_followers(user)]}, %{user: user})
returned_activities =
ActivityBuilder.insert_list(10, %{"to" => [User.ap_followers(user)]}, %{user: user})
other_user = insert(:user)
ActivityBuilder.insert_list(10, %{}, %{user: other_user})
since_id = List.last(activities).id
current_user =
Changeset.change(current_user, following: [User.ap_followers(user)])
|> Repo.update!()
conn =
conn
|> with_credentials(current_user.nickname, "test")
|> get("/api/statuses/friends_timeline.json", %{since_id: since_id})
response = json_response(conn, 200)
assert length(response) == 10
assert response ==
Enum.map(returned_activities, fn activity ->
ActivityView.render("activity.json", %{
activity: activity,
user: User.get_cached_by_ap_id(activity.data["actor"]),
for: current_user
})
end)
end
end
describe "GET /statuses/dm_timeline.json" do
test "it show direct messages", %{conn: conn} do
user_one = insert(:user)
user_two = insert(:user)
{:ok, user_two} = User.follow(user_two, user_one)
{:ok, direct} =
CommonAPI.post(user_one, %{
"status" => "Hi @#{user_two.nickname}!",
"visibility" => "direct"
})
{:ok, direct_two} =
CommonAPI.post(user_two, %{
"status" => "Hi @#{user_one.nickname}!",
"visibility" => "direct"
})
{:ok, _follower_only} =
CommonAPI.post(user_one, %{
"status" => "Hi @#{user_two.nickname}!",
"visibility" => "private"
})
# Only direct should be visible here
res_conn =
conn
|> assign(:user, user_two)
|> get("/api/statuses/dm_timeline.json")
[status, status_two] = json_response(res_conn, 200)
assert status["id"] == direct_two.id
assert status_two["id"] == direct.id
end
test "doesn't include DMs from blocked users", %{conn: conn} do
blocker = insert(:user)
blocked = insert(:user)
user = insert(:user)
{:ok, blocker} = User.block(blocker, blocked)
{:ok, _blocked_direct} =
CommonAPI.post(blocked, %{
"status" => "Hi @#{blocker.nickname}!",
"visibility" => "direct"
})
{:ok, direct} =
CommonAPI.post(user, %{
"status" => "Hi @#{blocker.nickname}!",
"visibility" => "direct"
})
res_conn =
conn
|> assign(:user, blocker)
|> get("/api/statuses/dm_timeline.json")
[status] = json_response(res_conn, 200)
assert status["id"] == direct.id
end
end
describe "GET /statuses/mentions.json" do
setup [:valid_user]
test "without valid credentials", %{conn: conn} do
conn = get(conn, "/api/statuses/mentions.json")
assert json_response(conn, 403) == %{"error" => "Invalid credentials."}
end
test "with credentials", %{conn: conn, user: current_user} do
{:ok, activity} =
CommonAPI.post(current_user, %{
"status" => "why is tenshi eating a corndog so cute?",
"visibility" => "public"
})
conn =
conn
|> with_credentials(current_user.nickname, "test")
|> get("/api/statuses/mentions.json")
response = json_response(conn, 200)
assert length(response) == 1
assert Enum.at(response, 0) ==
ActivityView.render("activity.json", %{
user: current_user,
for: current_user,
activity: activity
})
end
test "does not show DMs in mentions timeline", %{conn: conn, user: current_user} do
{:ok, _activity} =
CommonAPI.post(current_user, %{
"status" => "Have you guys ever seen how cute tenshi eating a corndog is?",
"visibility" => "direct"
})
conn =
conn
|> with_credentials(current_user.nickname, "test")
|> get("/api/statuses/mentions.json")
response = json_response(conn, 200)
assert Enum.empty?(response)
end
end
describe "GET /api/qvitter/statuses/notifications.json" do
setup [:valid_user]
test "without valid credentials", %{conn: conn} do
conn = get(conn, "/api/qvitter/statuses/notifications.json")
assert json_response(conn, 403) == %{"error" => "Invalid credentials."}
end
test "with credentials", %{conn: conn, user: current_user} do
other_user = insert(:user)
{:ok, _activity} =
ActivityBuilder.insert(%{"to" => [current_user.ap_id]}, %{user: other_user})
conn =
conn
|> with_credentials(current_user.nickname, "test")
|> get("/api/qvitter/statuses/notifications.json")
response = json_response(conn, 200)
assert length(response) == 1
assert response ==
NotificationView.render("notification.json", %{
notifications: Notification.for_user(current_user),
for: current_user
})
end
+
+ test "muted user", %{conn: conn, user: current_user} do
+ other_user = insert(:user)
+
+ {:ok, current_user} = User.mute(current_user, other_user)
+
+ {:ok, _activity} =
+ ActivityBuilder.insert(%{"to" => [current_user.ap_id]}, %{user: other_user})
+
+ conn =
+ conn
+ |> with_credentials(current_user.nickname, "test")
+ |> get("/api/qvitter/statuses/notifications.json")
+
+ assert json_response(conn, 200) == []
+ end
+
+ test "muted user with with_muted parameter", %{conn: conn, user: current_user} do
+ other_user = insert(:user)
+
+ {:ok, current_user} = User.mute(current_user, other_user)
+
+ {:ok, _activity} =
+ ActivityBuilder.insert(%{"to" => [current_user.ap_id]}, %{user: other_user})
+
+ conn =
+ conn
+ |> with_credentials(current_user.nickname, "test")
+ |> get("/api/qvitter/statuses/notifications.json", %{"with_muted" => "true"})
+
+ assert length(json_response(conn, 200)) == 1
+ end
end
describe "POST /api/qvitter/statuses/notifications/read" do
setup [:valid_user]
test "without valid credentials", %{conn: conn} do
conn = post(conn, "/api/qvitter/statuses/notifications/read", %{"latest_id" => 1_234_567})
assert json_response(conn, 403) == %{"error" => "Invalid credentials."}
end
test "with credentials, without any params", %{conn: conn, user: current_user} do
conn =
conn
|> with_credentials(current_user.nickname, "test")
|> post("/api/qvitter/statuses/notifications/read")
assert json_response(conn, 400) == %{
"error" => "You need to specify latest_id",
"request" => "/api/qvitter/statuses/notifications/read"
}
end
test "with credentials, with params", %{conn: conn, user: current_user} do
other_user = insert(:user)
{:ok, _activity} =
ActivityBuilder.insert(%{"to" => [current_user.ap_id]}, %{user: other_user})
response_conn =
conn
|> with_credentials(current_user.nickname, "test")
|> get("/api/qvitter/statuses/notifications.json")
[notification] = response = json_response(response_conn, 200)
assert length(response) == 1
assert notification["is_seen"] == 0
response_conn =
conn
|> with_credentials(current_user.nickname, "test")
|> post("/api/qvitter/statuses/notifications/read", %{"latest_id" => notification["id"]})
[notification] = response = json_response(response_conn, 200)
assert length(response) == 1
assert notification["is_seen"] == 1
end
end
describe "GET /statuses/user_timeline.json" do
setup [:valid_user]
test "without any params", %{conn: conn} do
conn = get(conn, "/api/statuses/user_timeline.json")
assert json_response(conn, 400) == %{
"error" => "You need to specify screen_name or user_id",
"request" => "/api/statuses/user_timeline.json"
}
end
test "with user_id", %{conn: conn} do
user = insert(:user)
{:ok, activity} = ActivityBuilder.insert(%{"id" => 1}, %{user: user})
conn = get(conn, "/api/statuses/user_timeline.json", %{"user_id" => user.id})
response = json_response(conn, 200)
assert length(response) == 1
assert Enum.at(response, 0) ==
ActivityView.render("activity.json", %{user: user, activity: activity})
end
test "with screen_name", %{conn: conn} do
user = insert(:user)
{:ok, activity} = ActivityBuilder.insert(%{"id" => 1}, %{user: user})
conn = get(conn, "/api/statuses/user_timeline.json", %{"screen_name" => user.nickname})
response = json_response(conn, 200)
assert length(response) == 1
assert Enum.at(response, 0) ==
ActivityView.render("activity.json", %{user: user, activity: activity})
end
test "with credentials", %{conn: conn, user: current_user} do
{:ok, activity} = ActivityBuilder.insert(%{"id" => 1}, %{user: current_user})
conn =
conn
|> with_credentials(current_user.nickname, "test")
|> get("/api/statuses/user_timeline.json")
response = json_response(conn, 200)
assert length(response) == 1
assert Enum.at(response, 0) ==
ActivityView.render("activity.json", %{
user: current_user,
for: current_user,
activity: activity
})
end
test "with credentials with user_id", %{conn: conn, user: current_user} do
user = insert(:user)
{:ok, activity} = ActivityBuilder.insert(%{"id" => 1}, %{user: user})
conn =
conn
|> with_credentials(current_user.nickname, "test")
|> get("/api/statuses/user_timeline.json", %{"user_id" => user.id})
response = json_response(conn, 200)
assert length(response) == 1
assert Enum.at(response, 0) ==
ActivityView.render("activity.json", %{user: user, activity: activity})
end
test "with credentials screen_name", %{conn: conn, user: current_user} do
user = insert(:user)
{:ok, activity} = ActivityBuilder.insert(%{"id" => 1}, %{user: user})
conn =
conn
|> with_credentials(current_user.nickname, "test")
|> get("/api/statuses/user_timeline.json", %{"screen_name" => user.nickname})
response = json_response(conn, 200)
assert length(response) == 1
assert Enum.at(response, 0) ==
ActivityView.render("activity.json", %{user: user, activity: activity})
end
test "with credentials with user_id, excluding RTs", %{conn: conn, user: current_user} do
user = insert(:user)
{:ok, activity} = ActivityBuilder.insert(%{"id" => 1, "type" => "Create"}, %{user: user})
{:ok, _} = ActivityBuilder.insert(%{"id" => 2, "type" => "Announce"}, %{user: user})
conn =
conn
|> with_credentials(current_user.nickname, "test")
|> get("/api/statuses/user_timeline.json", %{
"user_id" => user.id,
"include_rts" => "false"
})
response = json_response(conn, 200)
assert length(response) == 1
assert Enum.at(response, 0) ==
ActivityView.render("activity.json", %{user: user, activity: activity})
conn =
conn
|> get("/api/statuses/user_timeline.json", %{"user_id" => user.id, "include_rts" => "0"})
response = json_response(conn, 200)
assert length(response) == 1
assert Enum.at(response, 0) ==
ActivityView.render("activity.json", %{user: user, activity: activity})
end
end
describe "POST /friendships/create.json" do
setup [:valid_user]
test "without valid credentials", %{conn: conn} do
conn = post(conn, "/api/friendships/create.json")
assert json_response(conn, 403) == %{"error" => "Invalid credentials."}
end
test "with credentials", %{conn: conn, user: current_user} do
followed = insert(:user)
conn =
conn
|> with_credentials(current_user.nickname, "test")
|> post("/api/friendships/create.json", %{user_id: followed.id})
current_user = User.get_cached_by_id(current_user.id)
assert User.ap_followers(followed) in current_user.following
assert json_response(conn, 200) ==
UserView.render("show.json", %{user: followed, for: current_user})
end
test "for restricted account", %{conn: conn, user: current_user} do
followed = insert(:user, info: %User.Info{locked: true})
conn =
conn
|> with_credentials(current_user.nickname, "test")
|> post("/api/friendships/create.json", %{user_id: followed.id})
current_user = User.get_cached_by_id(current_user.id)
followed = User.get_cached_by_id(followed.id)
refute User.ap_followers(followed) in current_user.following
assert json_response(conn, 200) ==
UserView.render("show.json", %{user: followed, for: current_user})
end
end
describe "POST /friendships/destroy.json" do
setup [:valid_user]
test "without valid credentials", %{conn: conn} do
conn = post(conn, "/api/friendships/destroy.json")
assert json_response(conn, 403) == %{"error" => "Invalid credentials."}
end
test "with credentials", %{conn: conn, user: current_user} do
followed = insert(:user)
{:ok, current_user} = User.follow(current_user, followed)
assert User.ap_followers(followed) in current_user.following
ActivityPub.follow(current_user, followed)
conn =
conn
|> with_credentials(current_user.nickname, "test")
|> post("/api/friendships/destroy.json", %{user_id: followed.id})
current_user = User.get_cached_by_id(current_user.id)
assert current_user.following == [current_user.ap_id]
assert json_response(conn, 200) ==
UserView.render("show.json", %{user: followed, for: current_user})
end
end
describe "POST /blocks/create.json" do
setup [:valid_user]
test "without valid credentials", %{conn: conn} do
conn = post(conn, "/api/blocks/create.json")
assert json_response(conn, 403) == %{"error" => "Invalid credentials."}
end
test "with credentials", %{conn: conn, user: current_user} do
blocked = insert(:user)
conn =
conn
|> with_credentials(current_user.nickname, "test")
|> post("/api/blocks/create.json", %{user_id: blocked.id})
current_user = User.get_cached_by_id(current_user.id)
assert User.blocks?(current_user, blocked)
assert json_response(conn, 200) ==
UserView.render("show.json", %{user: blocked, for: current_user})
end
end
describe "POST /blocks/destroy.json" do
setup [:valid_user]
test "without valid credentials", %{conn: conn} do
conn = post(conn, "/api/blocks/destroy.json")
assert json_response(conn, 403) == %{"error" => "Invalid credentials."}
end
test "with credentials", %{conn: conn, user: current_user} do
blocked = insert(:user)
{:ok, current_user, blocked} = TwitterAPI.block(current_user, %{"user_id" => blocked.id})
assert User.blocks?(current_user, blocked)
conn =
conn
|> with_credentials(current_user.nickname, "test")
|> post("/api/blocks/destroy.json", %{user_id: blocked.id})
current_user = User.get_cached_by_id(current_user.id)
assert current_user.info.blocks == []
assert json_response(conn, 200) ==
UserView.render("show.json", %{user: blocked, for: current_user})
end
end
describe "GET /help/test.json" do
test "returns \"ok\"", %{conn: conn} do
conn = get(conn, "/api/help/test.json")
assert json_response(conn, 200) == "ok"
end
end
describe "POST /api/qvitter/update_avatar.json" do
setup [:valid_user]
test "without valid credentials", %{conn: conn} do
conn = post(conn, "/api/qvitter/update_avatar.json")
assert json_response(conn, 403) == %{"error" => "Invalid credentials."}
end
test "with credentials", %{conn: conn, user: current_user} do
avatar_image = File.read!("test/fixtures/avatar_data_uri")
conn =
conn
|> with_credentials(current_user.nickname, "test")
|> post("/api/qvitter/update_avatar.json", %{img: avatar_image})
current_user = User.get_cached_by_id(current_user.id)
assert is_map(current_user.avatar)
assert json_response(conn, 200) ==
UserView.render("show.json", %{user: current_user, for: current_user})
end
test "user avatar can be reset", %{conn: conn, user: current_user} do
conn =
conn
|> with_credentials(current_user.nickname, "test")
|> post("/api/qvitter/update_avatar.json", %{img: ""})
current_user = User.get_cached_by_id(current_user.id)
assert current_user.avatar == nil
assert json_response(conn, 200) ==
UserView.render("show.json", %{user: current_user, for: current_user})
end
end
describe "GET /api/qvitter/mutes.json" do
setup [:valid_user]
test "unimplemented mutes without valid credentials", %{conn: conn} do
conn = get(conn, "/api/qvitter/mutes.json")
assert json_response(conn, 403) == %{"error" => "Invalid credentials."}
end
test "unimplemented mutes with credentials", %{conn: conn, user: current_user} do
response =
conn
|> with_credentials(current_user.nickname, "test")
|> get("/api/qvitter/mutes.json")
|> json_response(200)
assert [] = response
end
end
describe "POST /api/favorites/create/:id" do
setup [:valid_user]
test "without valid credentials", %{conn: conn} do
note_activity = insert(:note_activity)
conn = post(conn, "/api/favorites/create/#{note_activity.id}.json")
assert json_response(conn, 403) == %{"error" => "Invalid credentials."}
end
test "with credentials", %{conn: conn, user: current_user} do
note_activity = insert(:note_activity)
conn =
conn
|> with_credentials(current_user.nickname, "test")
|> post("/api/favorites/create/#{note_activity.id}.json")
assert json_response(conn, 200)
end
test "with credentials, invalid param", %{conn: conn, user: current_user} do
conn =
conn
|> with_credentials(current_user.nickname, "test")
|> post("/api/favorites/create/wrong.json")
assert json_response(conn, 400)
end
test "with credentials, invalid activity", %{conn: conn, user: current_user} do
conn =
conn
|> with_credentials(current_user.nickname, "test")
|> post("/api/favorites/create/1.json")
assert json_response(conn, 400)
end
end
describe "POST /api/favorites/destroy/:id" do
setup [:valid_user]
test "without valid credentials", %{conn: conn} do
note_activity = insert(:note_activity)
conn = post(conn, "/api/favorites/destroy/#{note_activity.id}.json")
assert json_response(conn, 403) == %{"error" => "Invalid credentials."}
end
test "with credentials", %{conn: conn, user: current_user} do
note_activity = insert(:note_activity)
object = Object.normalize(note_activity)
ActivityPub.like(current_user, object)
conn =
conn
|> with_credentials(current_user.nickname, "test")
|> post("/api/favorites/destroy/#{note_activity.id}.json")
assert json_response(conn, 200)
end
end
describe "POST /api/statuses/retweet/:id" do
setup [:valid_user]
test "without valid credentials", %{conn: conn} do
note_activity = insert(:note_activity)
conn = post(conn, "/api/statuses/retweet/#{note_activity.id}.json")
assert json_response(conn, 403) == %{"error" => "Invalid credentials."}
end
test "with credentials", %{conn: conn, user: current_user} do
note_activity = insert(:note_activity)
request_path = "/api/statuses/retweet/#{note_activity.id}.json"
response =
conn
|> with_credentials(current_user.nickname, "test")
|> post(request_path)
activity = Activity.get_by_id(note_activity.id)
activity_user = User.get_cached_by_ap_id(note_activity.data["actor"])
assert json_response(response, 200) ==
ActivityView.render("activity.json", %{
user: activity_user,
for: current_user,
activity: activity
})
end
end
describe "POST /api/statuses/unretweet/:id" do
setup [:valid_user]
test "without valid credentials", %{conn: conn} do
note_activity = insert(:note_activity)
conn = post(conn, "/api/statuses/unretweet/#{note_activity.id}.json")
assert json_response(conn, 403) == %{"error" => "Invalid credentials."}
end
test "with credentials", %{conn: conn, user: current_user} do
note_activity = insert(:note_activity)
request_path = "/api/statuses/retweet/#{note_activity.id}.json"
_response =
conn
|> with_credentials(current_user.nickname, "test")
|> post(request_path)
request_path = String.replace(request_path, "retweet", "unretweet")
response =
conn
|> with_credentials(current_user.nickname, "test")
|> post(request_path)
activity = Activity.get_by_id(note_activity.id)
activity_user = User.get_cached_by_ap_id(note_activity.data["actor"])
assert json_response(response, 200) ==
ActivityView.render("activity.json", %{
user: activity_user,
for: current_user,
activity: activity
})
end
end
describe "POST /api/account/register" do
test "it creates a new user", %{conn: conn} do
data = %{
"nickname" => "lain",
"email" => "lain@wired.jp",
"fullname" => "lain iwakura",
"bio" => "close the world.",
"password" => "bear",
"confirm" => "bear"
}
conn =
conn
|> post("/api/account/register", data)
user = json_response(conn, 200)
fetched_user = User.get_cached_by_nickname("lain")
assert user == UserView.render("show.json", %{user: fetched_user})
end
test "it returns errors on a problem", %{conn: conn} do
data = %{
"email" => "lain@wired.jp",
"fullname" => "lain iwakura",
"bio" => "close the world.",
"password" => "bear",
"confirm" => "bear"
}
conn =
conn
|> post("/api/account/register", data)
errors = json_response(conn, 400)
assert is_binary(errors["error"])
end
end
describe "POST /api/account/password_reset, with valid parameters" do
setup %{conn: conn} do
user = insert(:user)
conn = post(conn, "/api/account/password_reset?email=#{user.email}")
%{conn: conn, user: user}
end
test "it returns 204", %{conn: conn} do
assert json_response(conn, :no_content)
end
test "it creates a PasswordResetToken record for user", %{user: user} do
token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id)
assert token_record
end
test "it sends an email to user", %{user: user} do
token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id)
email = Pleroma.Emails.UserEmail.password_reset_email(user, token_record.token)
notify_email = Pleroma.Config.get([:instance, :notify_email])
instance_name = Pleroma.Config.get([:instance, :name])
assert_email_sent(
from: {instance_name, notify_email},
to: {user.name, user.email},
html_body: email.html_body
)
end
end
describe "POST /api/account/password_reset, with invalid parameters" do
setup [:valid_user]
test "it returns 500 when user is not found", %{conn: conn, user: user} do
conn = post(conn, "/api/account/password_reset?email=nonexisting_#{user.email}")
assert json_response(conn, :internal_server_error)
end
test "it returns 500 when user is not local", %{conn: conn, user: user} do
{:ok, user} = Repo.update(Changeset.change(user, local: false))
conn = post(conn, "/api/account/password_reset?email=#{user.email}")
assert json_response(conn, :internal_server_error)
end
end
describe "GET /api/account/confirm_email/:id/:token" do
setup do
user = insert(:user)
info_change = User.Info.confirmation_changeset(user.info, need_confirmation: true)
{:ok, user} =
user
|> Changeset.change()
|> Changeset.put_embed(:info, info_change)
|> Repo.update()
assert user.info.confirmation_pending
[user: user]
end
test "it redirects to root url", %{conn: conn, user: user} do
conn = get(conn, "/api/account/confirm_email/#{user.id}/#{user.info.confirmation_token}")
assert 302 == conn.status
end
test "it confirms the user account", %{conn: conn, user: user} do
get(conn, "/api/account/confirm_email/#{user.id}/#{user.info.confirmation_token}")
user = User.get_cached_by_id(user.id)
refute user.info.confirmation_pending
refute user.info.confirmation_token
end
test "it returns 500 if user cannot be found by id", %{conn: conn, user: user} do
conn = get(conn, "/api/account/confirm_email/0/#{user.info.confirmation_token}")
assert 500 == conn.status
end
test "it returns 500 if token is invalid", %{conn: conn, user: user} do
conn = get(conn, "/api/account/confirm_email/#{user.id}/wrong_token")
assert 500 == conn.status
end
end
describe "POST /api/account/resend_confirmation_email" do
setup do
setting = Pleroma.Config.get([:instance, :account_activation_required])
unless setting do
Pleroma.Config.put([:instance, :account_activation_required], true)
on_exit(fn -> Pleroma.Config.put([:instance, :account_activation_required], setting) end)
end
user = insert(:user)
info_change = User.Info.confirmation_changeset(user.info, need_confirmation: true)
{:ok, user} =
user
|> Changeset.change()
|> Changeset.put_embed(:info, info_change)
|> Repo.update()
assert user.info.confirmation_pending
[user: user]
end
test "it returns 204 No Content", %{conn: conn, user: user} do
conn
|> assign(:user, user)
|> post("/api/account/resend_confirmation_email?email=#{user.email}")
|> json_response(:no_content)
end
test "it sends confirmation email", %{conn: conn, user: user} do
conn
|> assign(:user, user)
|> post("/api/account/resend_confirmation_email?email=#{user.email}")
email = Pleroma.Emails.UserEmail.account_confirmation_email(user)
notify_email = Pleroma.Config.get([:instance, :notify_email])
instance_name = Pleroma.Config.get([:instance, :name])
assert_email_sent(
from: {instance_name, notify_email},
to: {user.name, user.email},
html_body: email.html_body
)
end
end
describe "GET /api/externalprofile/show" do
test "it returns the user", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
conn =
conn
|> assign(:user, user)
|> get("/api/externalprofile/show", %{profileurl: other_user.ap_id})
assert json_response(conn, 200) == UserView.render("show.json", %{user: other_user})
end
end
describe "GET /api/statuses/followers" do
test "it returns a user's followers", %{conn: conn} do
user = insert(:user)
follower_one = insert(:user)
follower_two = insert(:user)
_not_follower = insert(:user)
{:ok, follower_one} = User.follow(follower_one, user)
{:ok, follower_two} = User.follow(follower_two, user)
conn =
conn
|> assign(:user, user)
|> get("/api/statuses/followers")
expected = UserView.render("index.json", %{users: [follower_one, follower_two], for: user})
result = json_response(conn, 200)
assert Enum.sort(expected) == Enum.sort(result)
end
test "it returns 20 followers per page", %{conn: conn} do
user = insert(:user)
followers = insert_list(21, :user)
Enum.each(followers, fn follower ->
User.follow(follower, user)
end)
res_conn =
conn
|> assign(:user, user)
|> get("/api/statuses/followers")
result = json_response(res_conn, 200)
assert length(result) == 20
res_conn =
conn
|> assign(:user, user)
|> get("/api/statuses/followers?page=2")
result = json_response(res_conn, 200)
assert length(result) == 1
end
test "it returns a given user's followers with user_id", %{conn: conn} do
user = insert(:user)
follower_one = insert(:user)
follower_two = insert(:user)
not_follower = insert(:user)
{:ok, follower_one} = User.follow(follower_one, user)
{:ok, follower_two} = User.follow(follower_two, user)
conn =
conn
|> assign(:user, not_follower)
|> get("/api/statuses/followers", %{"user_id" => user.id})
assert MapSet.equal?(
MapSet.new(json_response(conn, 200)),
MapSet.new(
UserView.render("index.json", %{
users: [follower_one, follower_two],
for: not_follower
})
)
)
end
test "it returns empty when hide_followers is set to true", %{conn: conn} do
user = insert(:user, %{info: %{hide_followers: true}})
follower_one = insert(:user)
follower_two = insert(:user)
not_follower = insert(:user)
{:ok, _follower_one} = User.follow(follower_one, user)
{:ok, _follower_two} = User.follow(follower_two, user)
response =
conn
|> assign(:user, not_follower)
|> get("/api/statuses/followers", %{"user_id" => user.id})
|> json_response(200)
assert [] == response
end
test "it returns the followers when hide_followers is set to true if requested by the user themselves",
%{
conn: conn
} do
user = insert(:user, %{info: %{hide_followers: true}})
follower_one = insert(:user)
follower_two = insert(:user)
_not_follower = insert(:user)
{:ok, _follower_one} = User.follow(follower_one, user)
{:ok, _follower_two} = User.follow(follower_two, user)
conn =
conn
|> assign(:user, user)
|> get("/api/statuses/followers", %{"user_id" => user.id})
refute [] == json_response(conn, 200)
end
end
describe "GET /api/statuses/blocks" do
test "it returns the list of users blocked by requester", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, user} = User.block(user, other_user)
conn =
conn
|> assign(:user, user)
|> get("/api/statuses/blocks")
expected = UserView.render("index.json", %{users: [other_user], for: user})
result = json_response(conn, 200)
assert Enum.sort(expected) == Enum.sort(result)
end
end
describe "GET /api/statuses/friends" do
test "it returns the logged in user's friends", %{conn: conn} do
user = insert(:user)
followed_one = insert(:user)
followed_two = insert(:user)
_not_followed = insert(:user)
{:ok, user} = User.follow(user, followed_one)
{:ok, user} = User.follow(user, followed_two)
conn =
conn
|> assign(:user, user)
|> get("/api/statuses/friends")
expected = UserView.render("index.json", %{users: [followed_one, followed_two], for: user})
result = json_response(conn, 200)
assert Enum.sort(expected) == Enum.sort(result)
end
test "it returns 20 friends per page, except if 'export' is set to true", %{conn: conn} do
user = insert(:user)
followeds = insert_list(21, :user)
{:ok, user} =
Enum.reduce(followeds, {:ok, user}, fn followed, {:ok, user} ->
User.follow(user, followed)
end)
res_conn =
conn
|> assign(:user, user)
|> get("/api/statuses/friends")
result = json_response(res_conn, 200)
assert length(result) == 20
res_conn =
conn
|> assign(:user, user)
|> get("/api/statuses/friends", %{page: 2})
result = json_response(res_conn, 200)
assert length(result) == 1
res_conn =
conn
|> assign(:user, user)
|> get("/api/statuses/friends", %{all: true})
result = json_response(res_conn, 200)
assert length(result) == 21
end
test "it returns a given user's friends with user_id", %{conn: conn} do
user = insert(:user)
followed_one = insert(:user)
followed_two = insert(:user)
_not_followed = insert(:user)
{:ok, user} = User.follow(user, followed_one)
{:ok, user} = User.follow(user, followed_two)
conn =
conn
|> assign(:user, user)
|> get("/api/statuses/friends", %{"user_id" => user.id})
assert MapSet.equal?(
MapSet.new(json_response(conn, 200)),
MapSet.new(
UserView.render("index.json", %{users: [followed_one, followed_two], for: user})
)
)
end
test "it returns empty when hide_follows is set to true", %{conn: conn} do
user = insert(:user, %{info: %{hide_follows: true}})
followed_one = insert(:user)
followed_two = insert(:user)
not_followed = insert(:user)
{:ok, user} = User.follow(user, followed_one)
{:ok, user} = User.follow(user, followed_two)
conn =
conn
|> assign(:user, not_followed)
|> get("/api/statuses/friends", %{"user_id" => user.id})
assert [] == json_response(conn, 200)
end
test "it returns friends when hide_follows is set to true if the user themselves request it",
%{
conn: conn
} do
user = insert(:user, %{info: %{hide_follows: true}})
followed_one = insert(:user)
followed_two = insert(:user)
_not_followed = insert(:user)
{:ok, _user} = User.follow(user, followed_one)
{:ok, _user} = User.follow(user, followed_two)
response =
conn
|> assign(:user, user)
|> get("/api/statuses/friends", %{"user_id" => user.id})
|> json_response(200)
refute [] == response
end
test "it returns a given user's friends with screen_name", %{conn: conn} do
user = insert(:user)
followed_one = insert(:user)
followed_two = insert(:user)
_not_followed = insert(:user)
{:ok, user} = User.follow(user, followed_one)
{:ok, user} = User.follow(user, followed_two)
conn =
conn
|> assign(:user, user)
|> get("/api/statuses/friends", %{"screen_name" => user.nickname})
assert MapSet.equal?(
MapSet.new(json_response(conn, 200)),
MapSet.new(
UserView.render("index.json", %{users: [followed_one, followed_two], for: user})
)
)
end
end
describe "GET /friends/ids" do
test "it returns a user's friends", %{conn: conn} do
user = insert(:user)
followed_one = insert(:user)
followed_two = insert(:user)
_not_followed = insert(:user)
{:ok, user} = User.follow(user, followed_one)
{:ok, user} = User.follow(user, followed_two)
conn =
conn
|> assign(:user, user)
|> get("/api/friends/ids")
expected = [followed_one.id, followed_two.id]
assert MapSet.equal?(
MapSet.new(Poison.decode!(json_response(conn, 200))),
MapSet.new(expected)
)
end
end
describe "POST /api/account/update_profile.json" do
test "it updates a user's profile", %{conn: conn} do
user = insert(:user)
user2 = insert(:user)
conn =
conn
|> assign(:user, user)
|> post("/api/account/update_profile.json", %{
"name" => "new name",
"description" => "hi @#{user2.nickname}"
})
user = Repo.get!(User, user.id)
assert user.name == "new name"
assert user.bio ==
"hi <span class='h-card'><a data-user='#{user2.id}' class='u-url mention' href='#{
user2.ap_id
}'>@<span>#{user2.nickname}</span></a></span>"
assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user})
end
test "it sets and un-sets hide_follows", %{conn: conn} do
user = insert(:user)
conn
|> assign(:user, user)
|> post("/api/account/update_profile.json", %{
"hide_follows" => "true"
})
user = Repo.get!(User, user.id)
assert user.info.hide_follows == true
conn =
conn
|> assign(:user, user)
|> post("/api/account/update_profile.json", %{
"hide_follows" => "false"
})
user = refresh_record(user)
assert user.info.hide_follows == false
assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user})
end
test "it sets and un-sets hide_followers", %{conn: conn} do
user = insert(:user)
conn
|> assign(:user, user)
|> post("/api/account/update_profile.json", %{
"hide_followers" => "true"
})
user = Repo.get!(User, user.id)
assert user.info.hide_followers == true
conn =
conn
|> assign(:user, user)
|> post("/api/account/update_profile.json", %{
"hide_followers" => "false"
})
user = Repo.get!(User, user.id)
assert user.info.hide_followers == false
assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user})
end
test "it sets and un-sets show_role", %{conn: conn} do
user = insert(:user)
conn
|> assign(:user, user)
|> post("/api/account/update_profile.json", %{
"show_role" => "true"
})
user = Repo.get!(User, user.id)
assert user.info.show_role == true
conn =
conn
|> assign(:user, user)
|> post("/api/account/update_profile.json", %{
"show_role" => "false"
})
user = Repo.get!(User, user.id)
assert user.info.show_role == false
assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user})
end
test "it sets and un-sets skip_thread_containment", %{conn: conn} do
user = insert(:user)
response =
conn
|> assign(:user, user)
|> post("/api/account/update_profile.json", %{"skip_thread_containment" => "true"})
|> json_response(200)
assert response["pleroma"]["skip_thread_containment"] == true
user = refresh_record(user)
assert user.info.skip_thread_containment
response =
conn
|> assign(:user, user)
|> post("/api/account/update_profile.json", %{"skip_thread_containment" => "false"})
|> json_response(200)
assert response["pleroma"]["skip_thread_containment"] == false
refute refresh_record(user).info.skip_thread_containment
end
test "it locks an account", %{conn: conn} do
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> post("/api/account/update_profile.json", %{
"locked" => "true"
})
user = Repo.get!(User, user.id)
assert user.info.locked == true
assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user})
end
test "it unlocks an account", %{conn: conn} do
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> post("/api/account/update_profile.json", %{
"locked" => "false"
})
user = Repo.get!(User, user.id)
assert user.info.locked == false
assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user})
end
# Broken before the change to class="emoji" and non-<img/> in the DB
@tag :skip
test "it formats emojos", %{conn: conn} do
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> post("/api/account/update_profile.json", %{
"bio" => "I love our :moominmamma:​"
})
assert response = json_response(conn, 200)
assert %{
"description" => "I love our :moominmamma:",
"description_html" =>
~s{I love our <img class="emoji" alt="moominmamma" title="moominmamma" src="} <>
_
} = response
conn =
conn
|> get("/api/users/show.json?user_id=#{user.nickname}")
assert response == json_response(conn, 200)
end
end
defp valid_user(_context) do
user = insert(:user)
[user: user]
end
defp with_credentials(conn, username, password) do
header_content = "Basic " <> Base.encode64("#{username}:#{password}")
put_req_header(conn, "authorization", header_content)
end
describe "GET /api/search.json" do
test "it returns search results", %{conn: conn} do
user = insert(:user)
user_two = insert(:user, %{nickname: "shp@shitposter.club"})
{:ok, activity} = CommonAPI.post(user, %{"status" => "This is about 2hu"})
{:ok, _} = CommonAPI.post(user_two, %{"status" => "This isn't"})
conn =
conn
|> get("/api/search.json", %{"q" => "2hu", "page" => "1", "rpp" => "1"})
assert [status] = json_response(conn, 200)
assert status["id"] == activity.id
end
end
describe "GET /api/statusnet/tags/timeline/:tag.json" do
test "it returns the tags timeline", %{conn: conn} do
user = insert(:user)
user_two = insert(:user, %{nickname: "shp@shitposter.club"})
{:ok, activity} = CommonAPI.post(user, %{"status" => "This is about #2hu"})
{:ok, _} = CommonAPI.post(user_two, %{"status" => "This isn't"})
conn =
conn
|> get("/api/statusnet/tags/timeline/2hu.json")
assert [status] = json_response(conn, 200)
assert status["id"] == activity.id
end
end
test "Convert newlines to <br> in bio", %{conn: conn} do
user = insert(:user)
_conn =
conn
|> assign(:user, user)
|> post("/api/account/update_profile.json", %{
"description" => "Hello,\r\nWorld! I\n am a test."
})
user = Repo.get!(User, user.id)
assert user.bio == "Hello,<br>World! I<br> am a test."
end
describe "POST /api/pleroma/change_password" do
setup [:valid_user]
test "without credentials", %{conn: conn} do
conn = post(conn, "/api/pleroma/change_password")
assert json_response(conn, 403) == %{"error" => "Invalid credentials."}
end
test "with credentials and invalid password", %{conn: conn, user: current_user} do
conn =
conn
|> with_credentials(current_user.nickname, "test")
|> post("/api/pleroma/change_password", %{
"password" => "hi",
"new_password" => "newpass",
"new_password_confirmation" => "newpass"
})
assert json_response(conn, 200) == %{"error" => "Invalid password."}
end
test "with credentials, valid password and new password and confirmation not matching", %{
conn: conn,
user: current_user
} do
conn =
conn
|> with_credentials(current_user.nickname, "test")
|> post("/api/pleroma/change_password", %{
"password" => "test",
"new_password" => "newpass",
"new_password_confirmation" => "notnewpass"
})
assert json_response(conn, 200) == %{
"error" => "New password does not match confirmation."
}
end
test "with credentials, valid password and invalid new password", %{
conn: conn,
user: current_user
} do
conn =
conn
|> with_credentials(current_user.nickname, "test")
|> post("/api/pleroma/change_password", %{
"password" => "test",
"new_password" => "",
"new_password_confirmation" => ""
})
assert json_response(conn, 200) == %{
"error" => "New password can't be blank."
}
end
test "with credentials, valid password and matching new password and confirmation", %{
conn: conn,
user: current_user
} do
conn =
conn
|> with_credentials(current_user.nickname, "test")
|> post("/api/pleroma/change_password", %{
"password" => "test",
"new_password" => "newpass",
"new_password_confirmation" => "newpass"
})
assert json_response(conn, 200) == %{"status" => "success"}
fetched_user = User.get_cached_by_id(current_user.id)
assert Pbkdf2.checkpw("newpass", fetched_user.password_hash) == true
end
end
describe "POST /api/pleroma/delete_account" do
setup [:valid_user]
test "without credentials", %{conn: conn} do
conn = post(conn, "/api/pleroma/delete_account")
assert json_response(conn, 403) == %{"error" => "Invalid credentials."}
end
test "with credentials and invalid password", %{conn: conn, user: current_user} do
conn =
conn
|> with_credentials(current_user.nickname, "test")
|> post("/api/pleroma/delete_account", %{"password" => "hi"})
assert json_response(conn, 200) == %{"error" => "Invalid password."}
end
test "with credentials and valid password", %{conn: conn, user: current_user} do
conn =
conn
|> with_credentials(current_user.nickname, "test")
|> post("/api/pleroma/delete_account", %{"password" => "test"})
assert json_response(conn, 200) == %{"status" => "success"}
# Wait a second for the started task to end
:timer.sleep(1000)
end
end
describe "GET /api/pleroma/friend_requests" do
test "it lists friend requests" do
user = insert(:user)
other_user = insert(:user)
{:ok, _activity} = ActivityPub.follow(other_user, user)
user = User.get_cached_by_id(user.id)
other_user = User.get_cached_by_id(other_user.id)
assert User.following?(other_user, user) == false
conn =
build_conn()
|> assign(:user, user)
|> get("/api/pleroma/friend_requests")
assert [relationship] = json_response(conn, 200)
assert other_user.id == relationship["id"]
end
test "requires 'read' permission", %{conn: conn} do
token1 = insert(:oauth_token, scopes: ["write"])
token2 = insert(:oauth_token, scopes: ["read"])
for token <- [token1, token2] do
conn =
conn
|> put_req_header("authorization", "Bearer #{token.token}")
|> get("/api/pleroma/friend_requests")
if token == token1 do
assert %{"error" => "Insufficient permissions: read."} == json_response(conn, 403)
else
assert json_response(conn, 200)
end
end
end
end
describe "POST /api/pleroma/friendships/approve" do
test "it approves a friend request" do
user = insert(:user)
other_user = insert(:user)
{:ok, _activity} = ActivityPub.follow(other_user, user)
user = User.get_cached_by_id(user.id)
other_user = User.get_cached_by_id(other_user.id)
assert User.following?(other_user, user) == false
conn =
build_conn()
|> assign(:user, user)
|> post("/api/pleroma/friendships/approve", %{"user_id" => other_user.id})
assert relationship = json_response(conn, 200)
assert other_user.id == relationship["id"]
assert relationship["follows_you"] == true
end
end
describe "POST /api/pleroma/friendships/deny" do
test "it denies a friend request" do
user = insert(:user)
other_user = insert(:user)
{:ok, _activity} = ActivityPub.follow(other_user, user)
user = User.get_cached_by_id(user.id)
other_user = User.get_cached_by_id(other_user.id)
assert User.following?(other_user, user) == false
conn =
build_conn()
|> assign(:user, user)
|> post("/api/pleroma/friendships/deny", %{"user_id" => other_user.id})
assert relationship = json_response(conn, 200)
assert other_user.id == relationship["id"]
assert relationship["follows_you"] == false
end
end
describe "GET /api/pleroma/search_user" do
test "it returns users, ordered by similarity", %{conn: conn} do
user = insert(:user, %{name: "eal"})
user_two = insert(:user, %{name: "eal me"})
_user_three = insert(:user, %{name: "zzz"})
resp =
conn
|> get(twitter_api_search__path(conn, :search_user), query: "eal me")
|> json_response(200)
assert length(resp) == 2
assert [user_two.id, user.id] == Enum.map(resp, fn %{"id" => id} -> id end)
end
end
describe "POST /api/media/upload" do
setup context do
Pleroma.DataCase.ensure_local_uploader(context)
end
test "it performs the upload and sets `data[actor]` with AP id of uploader user", %{
conn: conn
} do
user = insert(:user)
upload_filename = "test/fixtures/image_tmp.jpg"
File.cp!("test/fixtures/image.jpg", upload_filename)
file = %Plug.Upload{
content_type: "image/jpg",
path: Path.absname(upload_filename),
filename: "image.jpg"
}
response =
conn
|> assign(:user, user)
|> put_req_header("content-type", "application/octet-stream")
|> post("/api/media/upload", %{
"media" => file
})
|> json_response(:ok)
assert response["media_id"]
object = Repo.get(Object, response["media_id"])
assert object
assert object.data["actor"] == User.ap_id(user)
end
end
describe "POST /api/media/metadata/create" do
setup do
object = insert(:note)
user = User.get_cached_by_ap_id(object.data["actor"])
%{object: object, user: user}
end
test "it returns :forbidden status on attempt to modify someone else's upload", %{
conn: conn,
object: object
} do
initial_description = object.data["name"]
another_user = insert(:user)
conn
|> assign(:user, another_user)
|> post("/api/media/metadata/create", %{"media_id" => object.id})
|> json_response(:forbidden)
object = Repo.get(Object, object.id)
assert object.data["name"] == initial_description
end
test "it updates `data[name]` of referenced Object with provided value", %{
conn: conn,
object: object,
user: user
} do
description = "Informative description of the image. Initial value: #{object.data["name"]}}"
conn
|> assign(:user, user)
|> post("/api/media/metadata/create", %{
"media_id" => object.id,
"alt_text" => %{"text" => description}
})
|> json_response(:no_content)
object = Repo.get(Object, object.id)
assert object.data["name"] == description
end
end
describe "POST /api/statuses/user_timeline.json?user_id=:user_id&pinned=true" do
test "it returns a list of pinned statuses", %{conn: conn} do
Pleroma.Config.put([:instance, :max_pinned_statuses], 1)
user = insert(:user, %{name: "egor"})
{:ok, %{id: activity_id}} = CommonAPI.post(user, %{"status" => "HI!!!"})
{:ok, _} = CommonAPI.pin(activity_id, user)
resp =
conn
|> get("/api/statuses/user_timeline.json", %{user_id: user.id, pinned: true})
|> json_response(200)
assert length(resp) == 1
assert [%{"id" => ^activity_id, "pinned" => true}] = resp
end
end
describe "POST /api/statuses/pin/:id" do
setup do
Pleroma.Config.put([:instance, :max_pinned_statuses], 1)
[user: insert(:user)]
end
test "without valid credentials", %{conn: conn} do
note_activity = insert(:note_activity)
conn = post(conn, "/api/statuses/pin/#{note_activity.id}.json")
assert json_response(conn, 403) == %{"error" => "Invalid credentials."}
end
test "with credentials", %{conn: conn, user: user} do
{:ok, activity} = CommonAPI.post(user, %{"status" => "test!"})
request_path = "/api/statuses/pin/#{activity.id}.json"
response =
conn
|> with_credentials(user.nickname, "test")
|> post(request_path)
user = refresh_record(user)
assert json_response(response, 200) ==
ActivityView.render("activity.json", %{user: user, for: user, activity: activity})
end
end
describe "POST /api/statuses/unpin/:id" do
setup do
Pleroma.Config.put([:instance, :max_pinned_statuses], 1)
[user: insert(:user)]
end
test "without valid credentials", %{conn: conn} do
note_activity = insert(:note_activity)
conn = post(conn, "/api/statuses/unpin/#{note_activity.id}.json")
assert json_response(conn, 403) == %{"error" => "Invalid credentials."}
end
test "with credentials", %{conn: conn, user: user} do
{:ok, activity} = CommonAPI.post(user, %{"status" => "test!"})
{:ok, activity} = CommonAPI.pin(activity.id, user)
request_path = "/api/statuses/unpin/#{activity.id}.json"
response =
conn
|> with_credentials(user.nickname, "test")
|> post(request_path)
user = refresh_record(user)
assert json_response(response, 200) ==
ActivityView.render("activity.json", %{user: user, for: user, activity: activity})
end
end
describe "GET /api/oauth_tokens" do
setup do
token = insert(:oauth_token) |> Repo.preload(:user)
%{token: token}
end
test "renders list", %{token: token} do
response =
build_conn()
|> assign(:user, token.user)
|> get("/api/oauth_tokens")
keys =
json_response(response, 200)
|> hd()
|> Map.keys()
assert keys -- ["id", "app_name", "valid_until"] == []
end
test "revoke token", %{token: token} do
response =
build_conn()
|> assign(:user, token.user)
|> delete("/api/oauth_tokens/#{token.id}")
tokens = Token.get_user_tokens(token.user)
assert tokens == []
assert response.status == 201
end
end
end

File Metadata

Mime Type
text/x-diff
Expires
Sat, Oct 10, 2:12 PM (1 d, 7 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1785265
Default Alt Text
(232 KB)

Event Timeline