forked from AkkomaGang/akkoma
115d08a754
This is to run things like streaming notifications out, which will sometimes need data that is created by the transaction, but is streamed out asynchronously.
1053 lines
32 KiB
Elixir
1053 lines
32 KiB
Elixir
# Pleroma: A lightweight social networking server
|
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
|
# SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
defmodule Pleroma.Web.CommonAPITest do
|
|
use Pleroma.DataCase
|
|
alias Pleroma.Activity
|
|
alias Pleroma.Chat
|
|
alias Pleroma.Conversation.Participation
|
|
alias Pleroma.Notification
|
|
alias Pleroma.Object
|
|
alias Pleroma.User
|
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
|
alias Pleroma.Web.ActivityPub.Visibility
|
|
alias Pleroma.Web.AdminAPI.AccountView
|
|
alias Pleroma.Web.CommonAPI
|
|
|
|
import Pleroma.Factory
|
|
import Mock
|
|
|
|
require Pleroma.Constants
|
|
|
|
setup do: clear_config([:instance, :safe_dm_mentions])
|
|
setup do: clear_config([:instance, :limit])
|
|
setup do: clear_config([:instance, :max_pinned_statuses])
|
|
|
|
describe "posting chat messages" do
|
|
setup do: clear_config([:instance, :chat_limit])
|
|
|
|
test "it posts a chat message without content but with an attachment" do
|
|
author = insert(:user)
|
|
recipient = insert(:user)
|
|
|
|
file = %Plug.Upload{
|
|
content_type: "image/jpg",
|
|
path: Path.absname("test/fixtures/image.jpg"),
|
|
filename: "an_image.jpg"
|
|
}
|
|
|
|
{:ok, upload} = ActivityPub.upload(file, actor: author.ap_id)
|
|
|
|
with_mocks([
|
|
{
|
|
Pleroma.Web.Streamer,
|
|
[],
|
|
[
|
|
stream: fn _, _ ->
|
|
nil
|
|
end
|
|
]
|
|
},
|
|
{
|
|
Pleroma.Web.Push,
|
|
[],
|
|
[
|
|
send: fn _ -> nil end
|
|
]
|
|
}
|
|
]) do
|
|
{:ok, activity} =
|
|
CommonAPI.post_chat_message(
|
|
author,
|
|
recipient,
|
|
nil,
|
|
media_id: upload.id
|
|
)
|
|
|
|
notification =
|
|
Notification.for_user_and_activity(recipient, activity)
|
|
|> Repo.preload(:activity)
|
|
|
|
assert called(Pleroma.Web.Push.send(notification))
|
|
assert called(Pleroma.Web.Streamer.stream(["user", "user:notification"], notification))
|
|
|
|
assert activity
|
|
end
|
|
end
|
|
|
|
test "it adds html newlines" do
|
|
author = insert(:user)
|
|
recipient = insert(:user)
|
|
|
|
other_user = insert(:user)
|
|
|
|
{:ok, activity} =
|
|
CommonAPI.post_chat_message(
|
|
author,
|
|
recipient,
|
|
"uguu\nuguuu"
|
|
)
|
|
|
|
assert other_user.ap_id not in activity.recipients
|
|
|
|
object = Object.normalize(activity, false)
|
|
|
|
assert object.data["content"] == "uguu<br/>uguuu"
|
|
end
|
|
|
|
test "it linkifies" do
|
|
author = insert(:user)
|
|
recipient = insert(:user)
|
|
|
|
other_user = insert(:user)
|
|
|
|
{:ok, activity} =
|
|
CommonAPI.post_chat_message(
|
|
author,
|
|
recipient,
|
|
"https://example.org is the site of @#{other_user.nickname} #2hu"
|
|
)
|
|
|
|
assert other_user.ap_id not in activity.recipients
|
|
|
|
object = Object.normalize(activity, false)
|
|
|
|
assert object.data["content"] ==
|
|
"<a href=\"https://example.org\" rel=\"ugc\">https://example.org</a> is the site of <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{
|
|
other_user.id
|
|
}\" href=\"#{other_user.ap_id}\" rel=\"ugc\">@<span>#{other_user.nickname}</span></a></span> <a class=\"hashtag\" data-tag=\"2hu\" href=\"http://localhost:4001/tag/2hu\">#2hu</a>"
|
|
end
|
|
|
|
test "it posts a chat message" do
|
|
author = insert(:user)
|
|
recipient = insert(:user)
|
|
|
|
{:ok, activity} =
|
|
CommonAPI.post_chat_message(
|
|
author,
|
|
recipient,
|
|
"a test message <script>alert('uuu')</script> :firefox:"
|
|
)
|
|
|
|
assert activity.data["type"] == "Create"
|
|
assert activity.local
|
|
object = Object.normalize(activity)
|
|
|
|
assert object.data["type"] == "ChatMessage"
|
|
assert object.data["to"] == [recipient.ap_id]
|
|
|
|
assert object.data["content"] ==
|
|
"a test message <script>alert('uuu')</script> :firefox:"
|
|
|
|
assert object.data["emoji"] == %{
|
|
"firefox" => "http://localhost:4001/emoji/Firefox.gif"
|
|
}
|
|
|
|
assert Chat.get(author.id, recipient.ap_id)
|
|
assert Chat.get(recipient.id, author.ap_id)
|
|
|
|
assert :ok == Pleroma.Web.Federator.perform(:publish, activity)
|
|
end
|
|
|
|
test "it reject messages over the local limit" do
|
|
Pleroma.Config.put([:instance, :chat_limit], 2)
|
|
|
|
author = insert(:user)
|
|
recipient = insert(:user)
|
|
|
|
{:error, message} =
|
|
CommonAPI.post_chat_message(
|
|
author,
|
|
recipient,
|
|
"123"
|
|
)
|
|
|
|
assert message == :content_too_long
|
|
end
|
|
end
|
|
|
|
describe "unblocking" do
|
|
test "it works even without an existing block activity" do
|
|
blocked = insert(:user)
|
|
blocker = insert(:user)
|
|
User.block(blocker, blocked)
|
|
|
|
assert User.blocks?(blocker, blocked)
|
|
assert {:ok, :no_activity} == CommonAPI.unblock(blocker, blocked)
|
|
refute User.blocks?(blocker, blocked)
|
|
end
|
|
end
|
|
|
|
describe "deletion" do
|
|
test "it works with pruned objects" do
|
|
user = insert(:user)
|
|
|
|
{:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
|
|
|
|
clear_config([:instance, :federating], true)
|
|
|
|
Object.normalize(post, false)
|
|
|> Object.prune()
|
|
|
|
with_mock Pleroma.Web.Federator,
|
|
publish: fn _ -> nil end do
|
|
assert {:ok, delete} = CommonAPI.delete(post.id, user)
|
|
assert delete.local
|
|
assert called(Pleroma.Web.Federator.publish(delete))
|
|
end
|
|
|
|
refute Activity.get_by_id(post.id)
|
|
end
|
|
|
|
test "it allows users to delete their posts" do
|
|
user = insert(:user)
|
|
|
|
{:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
|
|
|
|
clear_config([:instance, :federating], true)
|
|
|
|
with_mock Pleroma.Web.Federator,
|
|
publish: fn _ -> nil end do
|
|
assert {:ok, delete} = CommonAPI.delete(post.id, user)
|
|
assert delete.local
|
|
assert called(Pleroma.Web.Federator.publish(delete))
|
|
end
|
|
|
|
refute Activity.get_by_id(post.id)
|
|
end
|
|
|
|
test "it does not allow a user to delete their posts" do
|
|
user = insert(:user)
|
|
other_user = insert(:user)
|
|
|
|
{:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
|
|
|
|
assert {:error, "Could not delete"} = CommonAPI.delete(post.id, other_user)
|
|
assert Activity.get_by_id(post.id)
|
|
end
|
|
|
|
test "it allows moderators to delete other user's posts" do
|
|
user = insert(:user)
|
|
moderator = insert(:user, is_moderator: true)
|
|
|
|
{:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
|
|
|
|
assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
|
|
assert delete.local
|
|
|
|
refute Activity.get_by_id(post.id)
|
|
end
|
|
|
|
test "it allows admins to delete other user's posts" do
|
|
user = insert(:user)
|
|
moderator = insert(:user, is_admin: true)
|
|
|
|
{:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
|
|
|
|
assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
|
|
assert delete.local
|
|
|
|
refute Activity.get_by_id(post.id)
|
|
end
|
|
|
|
test "superusers deleting non-local posts won't federate the delete" do
|
|
# This is the user of the ingested activity
|
|
_user =
|
|
insert(:user,
|
|
local: false,
|
|
ap_id: "http://mastodon.example.org/users/admin",
|
|
last_refreshed_at: NaiveDateTime.utc_now()
|
|
)
|
|
|
|
moderator = insert(:user, is_admin: true)
|
|
|
|
data =
|
|
File.read!("test/fixtures/mastodon-post-activity.json")
|
|
|> Jason.decode!()
|
|
|
|
{:ok, post} = Transmogrifier.handle_incoming(data)
|
|
|
|
with_mock Pleroma.Web.Federator,
|
|
publish: fn _ -> nil end do
|
|
assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
|
|
assert delete.local
|
|
refute called(Pleroma.Web.Federator.publish(:_))
|
|
end
|
|
|
|
refute Activity.get_by_id(post.id)
|
|
end
|
|
end
|
|
|
|
test "favoriting race condition" do
|
|
user = insert(:user)
|
|
users_serial = insert_list(10, :user)
|
|
users = insert_list(10, :user)
|
|
|
|
{:ok, activity} = CommonAPI.post(user, %{status: "."})
|
|
|
|
users_serial
|
|
|> Enum.map(fn user ->
|
|
CommonAPI.favorite(user, activity.id)
|
|
end)
|
|
|
|
object = Object.get_by_ap_id(activity.data["object"])
|
|
assert object.data["like_count"] == 10
|
|
|
|
users
|
|
|> Enum.map(fn user ->
|
|
Task.async(fn ->
|
|
CommonAPI.favorite(user, activity.id)
|
|
end)
|
|
end)
|
|
|> Enum.map(&Task.await/1)
|
|
|
|
object = Object.get_by_ap_id(activity.data["object"])
|
|
assert object.data["like_count"] == 20
|
|
end
|
|
|
|
test "repeating race condition" do
|
|
user = insert(:user)
|
|
users_serial = insert_list(10, :user)
|
|
users = insert_list(10, :user)
|
|
|
|
{:ok, activity} = CommonAPI.post(user, %{status: "."})
|
|
|
|
users_serial
|
|
|> Enum.map(fn user ->
|
|
CommonAPI.repeat(activity.id, user)
|
|
end)
|
|
|
|
object = Object.get_by_ap_id(activity.data["object"])
|
|
assert object.data["announcement_count"] == 10
|
|
|
|
users
|
|
|> Enum.map(fn user ->
|
|
Task.async(fn ->
|
|
CommonAPI.repeat(activity.id, user)
|
|
end)
|
|
end)
|
|
|> Enum.map(&Task.await/1)
|
|
|
|
object = Object.get_by_ap_id(activity.data["object"])
|
|
assert object.data["announcement_count"] == 20
|
|
end
|
|
|
|
test "when replying to a conversation / participation, it will set the correct context id even if no explicit reply_to is given" do
|
|
user = insert(:user)
|
|
{:ok, activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"})
|
|
|
|
[participation] = Participation.for_user(user)
|
|
|
|
{:ok, convo_reply} =
|
|
CommonAPI.post(user, %{status: ".", in_reply_to_conversation_id: participation.id})
|
|
|
|
assert Visibility.is_direct?(convo_reply)
|
|
|
|
assert activity.data["context"] == convo_reply.data["context"]
|
|
end
|
|
|
|
test "when replying to a conversation / participation, it only mentions the recipients explicitly declared in the participation" do
|
|
har = insert(:user)
|
|
jafnhar = insert(:user)
|
|
tridi = insert(:user)
|
|
|
|
{:ok, activity} =
|
|
CommonAPI.post(har, %{
|
|
status: "@#{jafnhar.nickname} hey",
|
|
visibility: "direct"
|
|
})
|
|
|
|
assert har.ap_id in activity.recipients
|
|
assert jafnhar.ap_id in activity.recipients
|
|
|
|
[participation] = Participation.for_user(har)
|
|
|
|
{:ok, activity} =
|
|
CommonAPI.post(har, %{
|
|
status: "I don't really like @#{tridi.nickname}",
|
|
visibility: "direct",
|
|
in_reply_to_status_id: activity.id,
|
|
in_reply_to_conversation_id: participation.id
|
|
})
|
|
|
|
assert har.ap_id in activity.recipients
|
|
assert jafnhar.ap_id in activity.recipients
|
|
refute tridi.ap_id in activity.recipients
|
|
end
|
|
|
|
test "with the safe_dm_mention option set, it does not mention people beyond the initial tags" do
|
|
har = insert(:user)
|
|
jafnhar = insert(:user)
|
|
tridi = insert(:user)
|
|
|
|
Pleroma.Config.put([:instance, :safe_dm_mentions], true)
|
|
|
|
{:ok, activity} =
|
|
CommonAPI.post(har, %{
|
|
status: "@#{jafnhar.nickname} hey, i never want to see @#{tridi.nickname} again",
|
|
visibility: "direct"
|
|
})
|
|
|
|
refute tridi.ap_id in activity.recipients
|
|
assert jafnhar.ap_id in activity.recipients
|
|
end
|
|
|
|
test "it de-duplicates tags" do
|
|
user = insert(:user)
|
|
{:ok, activity} = CommonAPI.post(user, %{status: "#2hu #2HU"})
|
|
|
|
object = Object.normalize(activity)
|
|
|
|
assert object.data["tag"] == ["2hu"]
|
|
end
|
|
|
|
test "it adds emoji in the object" do
|
|
user = insert(:user)
|
|
{:ok, activity} = CommonAPI.post(user, %{status: ":firefox:"})
|
|
|
|
assert Object.normalize(activity).data["emoji"]["firefox"]
|
|
end
|
|
|
|
describe "posting" do
|
|
test "it supports explicit addressing" do
|
|
user = insert(:user)
|
|
user_two = insert(:user)
|
|
user_three = insert(:user)
|
|
user_four = insert(:user)
|
|
|
|
{:ok, activity} =
|
|
CommonAPI.post(user, %{
|
|
status:
|
|
"Hey, I think @#{user_three.nickname} is ugly. @#{user_four.nickname} is alright though.",
|
|
to: [user_two.nickname, user_four.nickname, "nonexistent"]
|
|
})
|
|
|
|
assert user.ap_id in activity.recipients
|
|
assert user_two.ap_id in activity.recipients
|
|
assert user_four.ap_id in activity.recipients
|
|
refute user_three.ap_id in activity.recipients
|
|
end
|
|
|
|
test "it filters out obviously bad tags when accepting a post as HTML" do
|
|
user = insert(:user)
|
|
|
|
post = "<p><b>2hu</b></p><script>alert('xss')</script>"
|
|
|
|
{:ok, activity} =
|
|
CommonAPI.post(user, %{
|
|
status: post,
|
|
content_type: "text/html"
|
|
})
|
|
|
|
object = Object.normalize(activity)
|
|
|
|
assert object.data["content"] == "<p><b>2hu</b></p>alert('xss')"
|
|
end
|
|
|
|
test "it filters out obviously bad tags when accepting a post as Markdown" do
|
|
user = insert(:user)
|
|
|
|
post = "<p><b>2hu</b></p><script>alert('xss')</script>"
|
|
|
|
{:ok, activity} =
|
|
CommonAPI.post(user, %{
|
|
status: post,
|
|
content_type: "text/markdown"
|
|
})
|
|
|
|
object = Object.normalize(activity)
|
|
|
|
assert object.data["content"] == "<p><b>2hu</b></p>alert('xss')"
|
|
end
|
|
|
|
test "it does not allow replies to direct messages that are not direct messages themselves" do
|
|
user = insert(:user)
|
|
|
|
{:ok, activity} = CommonAPI.post(user, %{status: "suya..", visibility: "direct"})
|
|
|
|
assert {:ok, _} =
|
|
CommonAPI.post(user, %{
|
|
status: "suya..",
|
|
visibility: "direct",
|
|
in_reply_to_status_id: activity.id
|
|
})
|
|
|
|
Enum.each(["public", "private", "unlisted"], fn visibility ->
|
|
assert {:error, "The message visibility must be direct"} =
|
|
CommonAPI.post(user, %{
|
|
status: "suya..",
|
|
visibility: visibility,
|
|
in_reply_to_status_id: activity.id
|
|
})
|
|
end)
|
|
end
|
|
|
|
test "replying with a direct message will NOT auto-add the author of the reply to the recipient list" do
|
|
user = insert(:user)
|
|
other_user = insert(:user)
|
|
third_user = insert(:user)
|
|
|
|
{:ok, post} = CommonAPI.post(user, %{status: "I'm stupid"})
|
|
|
|
{:ok, open_answer} =
|
|
CommonAPI.post(other_user, %{status: "No ur smart", in_reply_to_status_id: post.id})
|
|
|
|
# The OP is implicitly added
|
|
assert user.ap_id in open_answer.recipients
|
|
|
|
{:ok, secret_answer} =
|
|
CommonAPI.post(other_user, %{
|
|
status: "lol, that guy really is stupid, right, @#{third_user.nickname}?",
|
|
in_reply_to_status_id: post.id,
|
|
visibility: "direct"
|
|
})
|
|
|
|
assert third_user.ap_id in secret_answer.recipients
|
|
|
|
# The OP is not added
|
|
refute user.ap_id in secret_answer.recipients
|
|
end
|
|
|
|
test "it allows to address a list" do
|
|
user = insert(:user)
|
|
{:ok, list} = Pleroma.List.create("foo", user)
|
|
|
|
{:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"})
|
|
|
|
assert activity.data["bcc"] == [list.ap_id]
|
|
assert activity.recipients == [list.ap_id, user.ap_id]
|
|
assert activity.data["listMessage"] == list.ap_id
|
|
end
|
|
|
|
test "it returns error when status is empty and no attachments" do
|
|
user = insert(:user)
|
|
|
|
assert {:error, "Cannot post an empty status without attachments"} =
|
|
CommonAPI.post(user, %{status: ""})
|
|
end
|
|
|
|
test "it validates character limits are correctly enforced" do
|
|
Pleroma.Config.put([:instance, :limit], 5)
|
|
|
|
user = insert(:user)
|
|
|
|
assert {:error, "The status is over the character limit"} =
|
|
CommonAPI.post(user, %{status: "foobar"})
|
|
|
|
assert {:ok, activity} = CommonAPI.post(user, %{status: "12345"})
|
|
end
|
|
|
|
test "it can handle activities that expire" do
|
|
user = insert(:user)
|
|
|
|
expires_at =
|
|
NaiveDateTime.utc_now()
|
|
|> NaiveDateTime.truncate(:second)
|
|
|> NaiveDateTime.add(1_000_000, :second)
|
|
|
|
assert {:ok, activity} = CommonAPI.post(user, %{status: "chai", expires_in: 1_000_000})
|
|
|
|
assert expiration = Pleroma.ActivityExpiration.get_by_activity_id(activity.id)
|
|
assert expiration.scheduled_at == expires_at
|
|
end
|
|
end
|
|
|
|
describe "reactions" do
|
|
test "reacting to a status with an emoji" do
|
|
user = insert(:user)
|
|
other_user = insert(:user)
|
|
|
|
{:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
|
|
|
|
{:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
|
|
|
|
assert reaction.data["actor"] == user.ap_id
|
|
assert reaction.data["content"] == "👍"
|
|
|
|
{:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
|
|
|
|
{:error, _} = CommonAPI.react_with_emoji(activity.id, user, ".")
|
|
end
|
|
|
|
test "unreacting to a status with an emoji" do
|
|
user = insert(:user)
|
|
other_user = insert(:user)
|
|
|
|
{:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
|
|
{:ok, reaction} = CommonAPI.react_with_emoji(activity.id, user, "👍")
|
|
|
|
{:ok, unreaction} = CommonAPI.unreact_with_emoji(activity.id, user, "👍")
|
|
|
|
assert unreaction.data["type"] == "Undo"
|
|
assert unreaction.data["object"] == reaction.data["id"]
|
|
assert unreaction.local
|
|
end
|
|
|
|
test "repeating a status" do
|
|
user = insert(:user)
|
|
other_user = insert(:user)
|
|
|
|
{:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
|
|
|
|
{:ok, %Activity{} = announce_activity} = CommonAPI.repeat(activity.id, user)
|
|
assert Visibility.is_public?(announce_activity)
|
|
end
|
|
|
|
test "can't repeat a repeat" do
|
|
user = insert(:user)
|
|
other_user = insert(:user)
|
|
{:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
|
|
|
|
{:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, other_user)
|
|
|
|
refute match?({:ok, %Activity{}}, CommonAPI.repeat(announce.id, user))
|
|
end
|
|
|
|
test "repeating a status privately" do
|
|
user = insert(:user)
|
|
other_user = insert(:user)
|
|
|
|
{:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
|
|
|
|
{:ok, %Activity{} = announce_activity} =
|
|
CommonAPI.repeat(activity.id, user, %{visibility: "private"})
|
|
|
|
assert Visibility.is_private?(announce_activity)
|
|
refute Visibility.visible_for_user?(announce_activity, nil)
|
|
end
|
|
|
|
test "favoriting a status" do
|
|
user = insert(:user)
|
|
other_user = insert(:user)
|
|
|
|
{:ok, post_activity} = CommonAPI.post(other_user, %{status: "cofe"})
|
|
|
|
{:ok, %Activity{data: data}} = CommonAPI.favorite(user, post_activity.id)
|
|
assert data["type"] == "Like"
|
|
assert data["actor"] == user.ap_id
|
|
assert data["object"] == post_activity.data["object"]
|
|
end
|
|
|
|
test "retweeting a status twice returns the status" do
|
|
user = insert(:user)
|
|
other_user = insert(:user)
|
|
|
|
{:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
|
|
{:ok, %Activity{} = announce} = CommonAPI.repeat(activity.id, user)
|
|
{:ok, ^announce} = CommonAPI.repeat(activity.id, user)
|
|
end
|
|
|
|
test "favoriting a status twice returns ok, but without the like activity" do
|
|
user = insert(:user)
|
|
other_user = insert(:user)
|
|
|
|
{:ok, activity} = CommonAPI.post(other_user, %{status: "cofe"})
|
|
{:ok, %Activity{}} = CommonAPI.favorite(user, activity.id)
|
|
assert {:ok, :already_liked} = CommonAPI.favorite(user, activity.id)
|
|
end
|
|
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 "pin status", %{user: user, activity: activity} do
|
|
assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
|
|
|
|
id = activity.id
|
|
user = refresh_record(user)
|
|
|
|
assert %User{pinned_activities: [^id]} = user
|
|
end
|
|
|
|
test "pin poll", %{user: user} do
|
|
{:ok, activity} =
|
|
CommonAPI.post(user, %{
|
|
status: "How is fediverse today?",
|
|
poll: %{options: ["Absolutely outstanding", "Not good"], expires_in: 20}
|
|
})
|
|
|
|
assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
|
|
|
|
id = activity.id
|
|
user = refresh_record(user)
|
|
|
|
assert %User{pinned_activities: [^id]} = user
|
|
end
|
|
|
|
test "unlisted statuses can be pinned", %{user: user} do
|
|
{:ok, activity} = CommonAPI.post(user, %{status: "HI!!!", visibility: "unlisted"})
|
|
assert {:ok, ^activity} = CommonAPI.pin(activity.id, user)
|
|
end
|
|
|
|
test "only self-authored can be pinned", %{activity: activity} do
|
|
user = insert(:user)
|
|
|
|
assert {:error, "Could not pin"} = CommonAPI.pin(activity.id, user)
|
|
end
|
|
|
|
test "max pinned statuses", %{user: user, activity: activity_one} do
|
|
{:ok, activity_two} = CommonAPI.post(user, %{status: "HI!!!"})
|
|
|
|
assert {:ok, ^activity_one} = CommonAPI.pin(activity_one.id, user)
|
|
|
|
user = refresh_record(user)
|
|
|
|
assert {:error, "You have already pinned the maximum number of statuses"} =
|
|
CommonAPI.pin(activity_two.id, user)
|
|
end
|
|
|
|
test "unpin status", %{user: user, activity: activity} do
|
|
{:ok, activity} = CommonAPI.pin(activity.id, user)
|
|
|
|
user = refresh_record(user)
|
|
|
|
id = activity.id
|
|
|
|
assert match?({:ok, %{id: ^id}}, CommonAPI.unpin(activity.id, user))
|
|
|
|
user = refresh_record(user)
|
|
|
|
assert %User{pinned_activities: []} = user
|
|
end
|
|
|
|
test "should unpin when deleting a status", %{user: user, activity: activity} do
|
|
{:ok, activity} = CommonAPI.pin(activity.id, user)
|
|
|
|
user = refresh_record(user)
|
|
|
|
assert {:ok, _} = CommonAPI.delete(activity.id, user)
|
|
|
|
user = refresh_record(user)
|
|
|
|
assert %User{pinned_activities: []} = user
|
|
end
|
|
end
|
|
|
|
describe "mute tests" do
|
|
setup do
|
|
user = insert(:user)
|
|
|
|
activity = insert(:note_activity)
|
|
|
|
[user: user, activity: activity]
|
|
end
|
|
|
|
test "add mute", %{user: user, activity: activity} do
|
|
{:ok, _} = CommonAPI.add_mute(user, activity)
|
|
assert CommonAPI.thread_muted?(user, activity)
|
|
end
|
|
|
|
test "remove mute", %{user: user, activity: activity} do
|
|
CommonAPI.add_mute(user, activity)
|
|
{:ok, _} = CommonAPI.remove_mute(user, activity)
|
|
refute CommonAPI.thread_muted?(user, activity)
|
|
end
|
|
|
|
test "check that mutes can't be duplicate", %{user: user, activity: activity} do
|
|
CommonAPI.add_mute(user, activity)
|
|
{:error, _} = CommonAPI.add_mute(user, activity)
|
|
end
|
|
end
|
|
|
|
describe "reports" do
|
|
test "creates a report" do
|
|
reporter = insert(:user)
|
|
target_user = insert(:user)
|
|
|
|
{:ok, activity} = CommonAPI.post(target_user, %{status: "foobar"})
|
|
|
|
reporter_ap_id = reporter.ap_id
|
|
target_ap_id = target_user.ap_id
|
|
activity_ap_id = activity.data["id"]
|
|
comment = "foobar"
|
|
|
|
report_data = %{
|
|
account_id: target_user.id,
|
|
comment: comment,
|
|
status_ids: [activity.id]
|
|
}
|
|
|
|
note_obj = %{
|
|
"type" => "Note",
|
|
"id" => activity_ap_id,
|
|
"content" => "foobar",
|
|
"published" => activity.object.data["published"],
|
|
"actor" => AccountView.render("show.json", %{user: target_user})
|
|
}
|
|
|
|
assert {:ok, flag_activity} = CommonAPI.report(reporter, report_data)
|
|
|
|
assert %Activity{
|
|
actor: ^reporter_ap_id,
|
|
data: %{
|
|
"type" => "Flag",
|
|
"content" => ^comment,
|
|
"object" => [^target_ap_id, ^note_obj],
|
|
"state" => "open"
|
|
}
|
|
} = flag_activity
|
|
end
|
|
|
|
test "updates report state" do
|
|
[reporter, target_user] = insert_pair(:user)
|
|
activity = insert(:note_activity, user: target_user)
|
|
|
|
{:ok, %Activity{id: report_id}} =
|
|
CommonAPI.report(reporter, %{
|
|
account_id: target_user.id,
|
|
comment: "I feel offended",
|
|
status_ids: [activity.id]
|
|
})
|
|
|
|
{:ok, report} = CommonAPI.update_report_state(report_id, "resolved")
|
|
|
|
assert report.data["state"] == "resolved"
|
|
|
|
[reported_user, activity_id] = report.data["object"]
|
|
|
|
assert reported_user == target_user.ap_id
|
|
assert activity_id == activity.data["id"]
|
|
end
|
|
|
|
test "does not update report state when state is unsupported" do
|
|
[reporter, target_user] = insert_pair(:user)
|
|
activity = insert(:note_activity, user: target_user)
|
|
|
|
{:ok, %Activity{id: report_id}} =
|
|
CommonAPI.report(reporter, %{
|
|
account_id: target_user.id,
|
|
comment: "I feel offended",
|
|
status_ids: [activity.id]
|
|
})
|
|
|
|
assert CommonAPI.update_report_state(report_id, "test") == {:error, "Unsupported state"}
|
|
end
|
|
|
|
test "updates state of multiple reports" do
|
|
[reporter, target_user] = insert_pair(:user)
|
|
activity = insert(:note_activity, user: target_user)
|
|
|
|
{:ok, %Activity{id: first_report_id}} =
|
|
CommonAPI.report(reporter, %{
|
|
account_id: target_user.id,
|
|
comment: "I feel offended",
|
|
status_ids: [activity.id]
|
|
})
|
|
|
|
{:ok, %Activity{id: second_report_id}} =
|
|
CommonAPI.report(reporter, %{
|
|
account_id: target_user.id,
|
|
comment: "I feel very offended!",
|
|
status_ids: [activity.id]
|
|
})
|
|
|
|
{:ok, report_ids} =
|
|
CommonAPI.update_report_state([first_report_id, second_report_id], "resolved")
|
|
|
|
first_report = Activity.get_by_id(first_report_id)
|
|
second_report = Activity.get_by_id(second_report_id)
|
|
|
|
assert report_ids -- [first_report_id, second_report_id] == []
|
|
assert first_report.data["state"] == "resolved"
|
|
assert second_report.data["state"] == "resolved"
|
|
end
|
|
end
|
|
|
|
describe "reblog muting" do
|
|
setup do
|
|
muter = insert(:user)
|
|
|
|
muted = insert(:user)
|
|
|
|
[muter: muter, muted: muted]
|
|
end
|
|
|
|
test "add a reblog mute", %{muter: muter, muted: muted} do
|
|
{:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
|
|
|
|
assert User.showing_reblogs?(muter, muted) == false
|
|
end
|
|
|
|
test "remove a reblog mute", %{muter: muter, muted: muted} do
|
|
{:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)
|
|
{:ok, _reblog_mute} = CommonAPI.show_reblogs(muter, muted)
|
|
|
|
assert User.showing_reblogs?(muter, muted) == true
|
|
end
|
|
end
|
|
|
|
describe "unfollow/2" do
|
|
test "also unsubscribes a user" do
|
|
[follower, followed] = insert_pair(:user)
|
|
{:ok, follower, followed, _} = CommonAPI.follow(follower, followed)
|
|
{:ok, _subscription} = User.subscribe(follower, followed)
|
|
|
|
assert User.subscribed_to?(follower, followed)
|
|
|
|
{:ok, follower} = CommonAPI.unfollow(follower, followed)
|
|
|
|
refute User.subscribed_to?(follower, followed)
|
|
end
|
|
|
|
test "cancels a pending follow for a local user" do
|
|
follower = insert(:user)
|
|
followed = insert(:user, locked: true)
|
|
|
|
assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
|
|
CommonAPI.follow(follower, followed)
|
|
|
|
assert User.get_follow_state(follower, followed) == :follow_pending
|
|
assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
|
|
assert User.get_follow_state(follower, followed) == nil
|
|
|
|
assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
|
|
Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
|
|
|
|
assert %{
|
|
data: %{
|
|
"type" => "Undo",
|
|
"object" => %{"type" => "Follow", "state" => "cancelled"}
|
|
}
|
|
} = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
|
|
end
|
|
|
|
test "cancels a pending follow for a remote user" do
|
|
follower = insert(:user)
|
|
followed = insert(:user, locked: true, local: false, ap_enabled: true)
|
|
|
|
assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
|
|
CommonAPI.follow(follower, followed)
|
|
|
|
assert User.get_follow_state(follower, followed) == :follow_pending
|
|
assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
|
|
assert User.get_follow_state(follower, followed) == nil
|
|
|
|
assert %{id: ^activity_id, data: %{"state" => "cancelled"}} =
|
|
Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed)
|
|
|
|
assert %{
|
|
data: %{
|
|
"type" => "Undo",
|
|
"object" => %{"type" => "Follow", "state" => "cancelled"}
|
|
}
|
|
} = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower)
|
|
end
|
|
end
|
|
|
|
describe "accept_follow_request/2" do
|
|
test "after acceptance, it sets all existing pending follow request states to 'accept'" do
|
|
user = insert(:user, locked: true)
|
|
follower = insert(:user)
|
|
follower_two = insert(:user)
|
|
|
|
{:ok, follow_activity} = ActivityPub.follow(follower, user)
|
|
{:ok, follow_activity_two} = ActivityPub.follow(follower, user)
|
|
{:ok, follow_activity_three} = ActivityPub.follow(follower_two, user)
|
|
|
|
assert follow_activity.data["state"] == "pending"
|
|
assert follow_activity_two.data["state"] == "pending"
|
|
assert follow_activity_three.data["state"] == "pending"
|
|
|
|
{:ok, _follower} = CommonAPI.accept_follow_request(follower, user)
|
|
|
|
assert Repo.get(Activity, follow_activity.id).data["state"] == "accept"
|
|
assert Repo.get(Activity, follow_activity_two.id).data["state"] == "accept"
|
|
assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
|
|
end
|
|
|
|
test "after rejection, it sets all existing pending follow request states to 'reject'" do
|
|
user = insert(:user, locked: true)
|
|
follower = insert(:user)
|
|
follower_two = insert(:user)
|
|
|
|
{:ok, follow_activity} = ActivityPub.follow(follower, user)
|
|
{:ok, follow_activity_two} = ActivityPub.follow(follower, user)
|
|
{:ok, follow_activity_three} = ActivityPub.follow(follower_two, user)
|
|
|
|
assert follow_activity.data["state"] == "pending"
|
|
assert follow_activity_two.data["state"] == "pending"
|
|
assert follow_activity_three.data["state"] == "pending"
|
|
|
|
{:ok, _follower} = CommonAPI.reject_follow_request(follower, user)
|
|
|
|
assert Repo.get(Activity, follow_activity.id).data["state"] == "reject"
|
|
assert Repo.get(Activity, follow_activity_two.id).data["state"] == "reject"
|
|
assert Repo.get(Activity, follow_activity_three.id).data["state"] == "pending"
|
|
end
|
|
|
|
test "doesn't create a following relationship if the corresponding follow request doesn't exist" do
|
|
user = insert(:user, locked: true)
|
|
not_follower = insert(:user)
|
|
CommonAPI.accept_follow_request(not_follower, user)
|
|
|
|
assert Pleroma.FollowingRelationship.following?(not_follower, user) == false
|
|
end
|
|
end
|
|
|
|
describe "vote/3" do
|
|
test "does not allow to vote twice" do
|
|
user = insert(:user)
|
|
other_user = insert(:user)
|
|
|
|
{:ok, activity} =
|
|
CommonAPI.post(user, %{
|
|
status: "Am I cute?",
|
|
poll: %{options: ["Yes", "No"], expires_in: 20}
|
|
})
|
|
|
|
object = Object.normalize(activity)
|
|
|
|
{:ok, _, object} = CommonAPI.vote(other_user, object, [0])
|
|
|
|
assert {:error, "Already voted"} == CommonAPI.vote(other_user, object, [1])
|
|
end
|
|
end
|
|
|
|
describe "listen/2" do
|
|
test "returns a valid activity" do
|
|
user = insert(:user)
|
|
|
|
{:ok, activity} =
|
|
CommonAPI.listen(user, %{
|
|
title: "lain radio episode 1",
|
|
album: "lain radio",
|
|
artist: "lain",
|
|
length: 180_000
|
|
})
|
|
|
|
object = Object.normalize(activity)
|
|
|
|
assert object.data["title"] == "lain radio episode 1"
|
|
|
|
assert Visibility.get_visibility(activity) == "public"
|
|
end
|
|
|
|
test "respects visibility=private" do
|
|
user = insert(:user)
|
|
|
|
{:ok, activity} =
|
|
CommonAPI.listen(user, %{
|
|
title: "lain radio episode 1",
|
|
album: "lain radio",
|
|
artist: "lain",
|
|
length: 180_000,
|
|
visibility: "private"
|
|
})
|
|
|
|
object = Object.normalize(activity)
|
|
|
|
assert object.data["title"] == "lain radio episode 1"
|
|
|
|
assert Visibility.get_visibility(activity) == "private"
|
|
end
|
|
end
|
|
end
|