1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.Web.ActivityPub.SideEffectsTest do
6 use Oban.Testing, repo: Pleroma.Repo
11 alias Pleroma.Chat.MessageReference
12 alias Pleroma.Notification
15 alias Pleroma.Tests.ObanHelpers
17 alias Pleroma.Web.ActivityPub.ActivityPub
18 alias Pleroma.Web.ActivityPub.Builder
19 alias Pleroma.Web.ActivityPub.SideEffects
20 alias Pleroma.Web.CommonAPI
22 import ExUnit.CaptureLog
24 import Pleroma.Factory
26 describe "handle_after_transaction" do
27 test "it streams out notifications and streams" do
28 author = insert(:user, local: true)
29 recipient = insert(:user, local: true)
31 {:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey")
33 {:ok, create_activity_data, _meta} =
34 Builder.create(author, chat_message_data["id"], [recipient.ap_id])
36 {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false)
38 {:ok, _create_activity, meta} =
39 SideEffects.handle(create_activity, local: false, object_data: chat_message_data)
41 assert [notification] = meta[:notifications]
48 stream: fn _, _ -> nil end
59 SideEffects.handle_after_transaction(meta)
61 assert called(Pleroma.Web.Streamer.stream(["user", "user:notification"], notification))
62 assert called(Pleroma.Web.Streamer.stream(["user", "user:pleroma_chat"], :_))
63 assert called(Pleroma.Web.Push.send(notification))
68 describe "blocking users" do
71 blocked = insert(:user)
72 User.follow(blocked, user)
73 User.follow(user, blocked)
75 {:ok, block_data, []} = Builder.block(user, blocked)
76 {:ok, block, _meta} = ActivityPub.persist(block_data, local: true)
78 %{user: user, blocked: blocked, block: block}
81 test "it unfollows and blocks", %{user: user, blocked: blocked, block: block} do
82 assert User.following?(user, blocked)
83 assert User.following?(blocked, user)
85 {:ok, _, _} = SideEffects.handle(block)
87 refute User.following?(user, blocked)
88 refute User.following?(blocked, user)
89 assert User.blocks?(user, blocked)
92 test "it blocks but does not unfollow if the relevant setting is set", %{
97 clear_config([:activitypub, :unfollow_blocked], false)
98 assert User.following?(user, blocked)
99 assert User.following?(blocked, user)
101 {:ok, _, _} = SideEffects.handle(block)
103 refute User.following?(user, blocked)
104 assert User.following?(blocked, user)
105 assert User.blocks?(user, blocked)
109 describe "update users" do
111 user = insert(:user, local: false)
112 {:ok, update_data, []} = Builder.update(user, %{"id" => user.ap_id, "name" => "new name!"})
113 {:ok, update, _meta} = ActivityPub.persist(update_data, local: true)
115 %{user: user, update_data: update_data, update: update}
118 test "it updates the user", %{user: user, update: update} do
119 {:ok, _, _} = SideEffects.handle(update)
120 user = User.get_by_id(user.id)
121 assert user.name == "new name!"
124 test "it uses a given changeset to update", %{user: user, update: update} do
125 changeset = Ecto.Changeset.change(user, %{default_scope: "direct"})
127 assert user.default_scope == "public"
128 {:ok, _, _} = SideEffects.handle(update, user_update_changeset: changeset)
129 user = User.get_by_id(user.id)
130 assert user.default_scope == "direct"
134 describe "delete objects" do
137 other_user = insert(:user)
139 {:ok, op} = CommonAPI.post(other_user, %{status: "big oof"})
140 {:ok, post} = CommonAPI.post(user, %{status: "hey", in_reply_to_id: op})
141 {:ok, favorite} = CommonAPI.favorite(user, post.id)
142 object = Object.normalize(post)
143 {:ok, delete_data, _meta} = Builder.delete(user, object.data["id"])
144 {:ok, delete_user_data, _meta} = Builder.delete(user, user.ap_id)
145 {:ok, delete, _meta} = ActivityPub.persist(delete_data, local: true)
146 {:ok, delete_user, _meta} = ActivityPub.persist(delete_user_data, local: true)
153 delete_user: delete_user,
159 test "it handles object deletions", %{
167 with_mock Pleroma.Web.ActivityPub.ActivityPub, [:passthrough],
168 stream_out: fn _ -> nil end,
169 stream_out_participations: fn _, _ -> nil end do
170 {:ok, delete, _} = SideEffects.handle(delete)
171 user = User.get_cached_by_ap_id(object.data["actor"])
173 assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out(delete))
174 assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out_participations(object, user))
177 object = Object.get_by_id(object.id)
178 assert object.data["type"] == "Tombstone"
179 refute Activity.get_by_id(post.id)
180 refute Activity.get_by_id(favorite.id)
182 user = User.get_by_id(user.id)
183 assert user.note_count == 0
185 object = Object.normalize(op.data["object"], false)
187 assert object.data["repliesCount"] == 0
190 test "it handles object deletions when the object itself has been pruned", %{
197 with_mock Pleroma.Web.ActivityPub.ActivityPub, [:passthrough],
198 stream_out: fn _ -> nil end,
199 stream_out_participations: fn _, _ -> nil end do
200 {:ok, delete, _} = SideEffects.handle(delete)
201 user = User.get_cached_by_ap_id(object.data["actor"])
203 assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out(delete))
204 assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out_participations(object, user))
207 object = Object.get_by_id(object.id)
208 assert object.data["type"] == "Tombstone"
209 refute Activity.get_by_id(post.id)
211 user = User.get_by_id(user.id)
212 assert user.note_count == 0
214 object = Object.normalize(op.data["object"], false)
216 assert object.data["repliesCount"] == 0
219 test "it handles user deletions", %{delete_user: delete, user: user} do
220 {:ok, _delete, _} = SideEffects.handle(delete)
221 ObanHelpers.perform_all()
223 assert User.get_cached_by_ap_id(user.ap_id).deactivated
226 test "it logs issues with objects deletion", %{
232 |> Object.change(%{data: Map.delete(object.data, "actor")})
235 Object.invalid_object_cache(object)
237 assert capture_log(fn ->
238 {:error, :no_object_actor} = SideEffects.handle(delete)
239 end) =~ "object doesn't have an actor"
243 describe "EmojiReact objects" do
245 poster = insert(:user)
248 {:ok, post} = CommonAPI.post(poster, %{status: "hey"})
250 {:ok, emoji_react_data, []} = Builder.emoji_react(user, post.object, "👌")
251 {:ok, emoji_react, _meta} = ActivityPub.persist(emoji_react_data, local: true)
253 %{emoji_react: emoji_react, user: user, poster: poster}
256 test "adds the reaction to the object", %{emoji_react: emoji_react, user: user} do
257 {:ok, emoji_react, _} = SideEffects.handle(emoji_react)
258 object = Object.get_by_ap_id(emoji_react.data["object"])
260 assert object.data["reaction_count"] == 1
261 assert ["👌", [user.ap_id]] in object.data["reactions"]
264 test "creates a notification", %{emoji_react: emoji_react, poster: poster} do
265 {:ok, emoji_react, _} = SideEffects.handle(emoji_react)
266 assert Repo.get_by(Notification, user_id: poster.id, activity_id: emoji_react.id)
270 describe "delete users with confirmation pending" do
272 user = insert(:user, confirmation_pending: true)
273 {:ok, delete_user_data, _meta} = Builder.delete(user, user.ap_id)
274 {:ok, delete_user, _meta} = ActivityPub.persist(delete_user_data, local: true)
275 {:ok, delete: delete_user, user: user}
278 test "when activation is not required", %{delete: delete, user: user} do
279 clear_config([:instance, :account_activation_required], false)
280 {:ok, _, _} = SideEffects.handle(delete)
281 ObanHelpers.perform_all()
283 assert User.get_cached_by_id(user.id).deactivated
286 test "when activation is required", %{delete: delete, user: user} do
287 clear_config([:instance, :account_activation_required], true)
288 {:ok, _, _} = SideEffects.handle(delete)
289 ObanHelpers.perform_all()
291 refute User.get_cached_by_id(user.id)
295 describe "Undo objects" do
297 poster = insert(:user)
299 {:ok, post} = CommonAPI.post(poster, %{status: "hey"})
300 {:ok, like} = CommonAPI.favorite(user, post.id)
301 {:ok, reaction} = CommonAPI.react_with_emoji(post.id, user, "👍")
302 {:ok, announce} = CommonAPI.repeat(post.id, user)
303 {:ok, block} = CommonAPI.block(user, poster)
305 {:ok, undo_data, _meta} = Builder.undo(user, like)
306 {:ok, like_undo, _meta} = ActivityPub.persist(undo_data, local: true)
308 {:ok, undo_data, _meta} = Builder.undo(user, reaction)
309 {:ok, reaction_undo, _meta} = ActivityPub.persist(undo_data, local: true)
311 {:ok, undo_data, _meta} = Builder.undo(user, announce)
312 {:ok, announce_undo, _meta} = ActivityPub.persist(undo_data, local: true)
314 {:ok, undo_data, _meta} = Builder.undo(user, block)
315 {:ok, block_undo, _meta} = ActivityPub.persist(undo_data, local: true)
318 like_undo: like_undo,
321 reaction_undo: reaction_undo,
323 announce_undo: announce_undo,
325 block_undo: block_undo,
332 test "deletes the original block", %{
333 block_undo: block_undo,
336 {:ok, _block_undo, _meta} = SideEffects.handle(block_undo)
338 refute Activity.get_by_id(block.id)
341 test "unblocks the blocked user", %{block_undo: block_undo, block: block} do
342 blocker = User.get_by_ap_id(block.data["actor"])
343 blocked = User.get_by_ap_id(block.data["object"])
345 {:ok, _block_undo, _} = SideEffects.handle(block_undo)
346 refute User.blocks?(blocker, blocked)
349 test "an announce undo removes the announce from the object", %{
350 announce_undo: announce_undo,
353 {:ok, _announce_undo, _} = SideEffects.handle(announce_undo)
355 object = Object.get_by_ap_id(post.data["object"])
357 assert object.data["announcement_count"] == 0
358 assert object.data["announcements"] == []
361 test "deletes the original announce", %{announce_undo: announce_undo, announce: announce} do
362 {:ok, _announce_undo, _} = SideEffects.handle(announce_undo)
363 refute Activity.get_by_id(announce.id)
366 test "a reaction undo removes the reaction from the object", %{
367 reaction_undo: reaction_undo,
370 {:ok, _reaction_undo, _} = SideEffects.handle(reaction_undo)
372 object = Object.get_by_ap_id(post.data["object"])
374 assert object.data["reaction_count"] == 0
375 assert object.data["reactions"] == []
378 test "deletes the original reaction", %{reaction_undo: reaction_undo, reaction: reaction} do
379 {:ok, _reaction_undo, _} = SideEffects.handle(reaction_undo)
380 refute Activity.get_by_id(reaction.id)
383 test "a like undo removes the like from the object", %{like_undo: like_undo, post: post} do
384 {:ok, _like_undo, _} = SideEffects.handle(like_undo)
386 object = Object.get_by_ap_id(post.data["object"])
388 assert object.data["like_count"] == 0
389 assert object.data["likes"] == []
392 test "deletes the original like", %{like_undo: like_undo, like: like} do
393 {:ok, _like_undo, _} = SideEffects.handle(like_undo)
394 refute Activity.get_by_id(like.id)
398 describe "like objects" do
400 poster = insert(:user)
402 {:ok, post} = CommonAPI.post(poster, %{status: "hey"})
404 {:ok, like_data, _meta} = Builder.like(user, post.object)
405 {:ok, like, _meta} = ActivityPub.persist(like_data, local: true)
407 %{like: like, user: user, poster: poster}
410 test "add the like to the original object", %{like: like, user: user} do
411 {:ok, like, _} = SideEffects.handle(like)
412 object = Object.get_by_ap_id(like.data["object"])
413 assert object.data["like_count"] == 1
414 assert user.ap_id in object.data["likes"]
417 test "creates a notification", %{like: like, poster: poster} do
418 {:ok, like, _} = SideEffects.handle(like)
419 assert Repo.get_by(Notification, user_id: poster.id, activity_id: like.id)
423 describe "creation of ChatMessages" do
424 test "notifies the recipient" do
425 author = insert(:user, local: false)
426 recipient = insert(:user, local: true)
428 {:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey")
430 {:ok, create_activity_data, _meta} =
431 Builder.create(author, chat_message_data["id"], [recipient.ap_id])
433 {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false)
435 {:ok, _create_activity, _meta} =
436 SideEffects.handle(create_activity, local: false, object_data: chat_message_data)
438 assert Repo.get_by(Notification, user_id: recipient.id, activity_id: create_activity.id)
441 test "it streams the created ChatMessage" do
442 author = insert(:user, local: true)
443 recipient = insert(:user, local: true)
445 {:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey")
447 {:ok, create_activity_data, _meta} =
448 Builder.create(author, chat_message_data["id"], [recipient.ap_id])
450 {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false)
452 {:ok, _create_activity, meta} =
453 SideEffects.handle(create_activity, local: false, object_data: chat_message_data)
455 assert [_, _] = meta[:streamables]
458 test "it creates a Chat and MessageReferences for the local users and bumps the unread count, except for the author" do
459 author = insert(:user, local: true)
460 recipient = insert(:user, local: true)
462 {:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey")
464 {:ok, create_activity_data, _meta} =
465 Builder.create(author, chat_message_data["id"], [recipient.ap_id])
467 {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false)
471 Pleroma.Web.Streamer,
474 stream: fn _, _ -> nil end
481 send: fn _ -> nil end
485 {:ok, _create_activity, meta} =
486 SideEffects.handle(create_activity, local: false, object_data: chat_message_data)
488 # The notification gets created
489 assert [notification] = meta[:notifications]
490 assert notification.activity_id == create_activity.id
492 # But it is not sent out
493 refute called(Pleroma.Web.Streamer.stream(["user", "user:notification"], notification))
494 refute called(Pleroma.Web.Push.send(notification))
496 # Same for the user chat stream
497 assert [{topics, _}, _] = meta[:streamables]
498 assert topics == ["user", "user:pleroma_chat"]
499 refute called(Pleroma.Web.Streamer.stream(["user", "user:pleroma_chat"], :_))
501 chat = Chat.get(author.id, recipient.ap_id)
503 [cm_ref] = MessageReference.for_chat_query(chat) |> Repo.all()
505 assert cm_ref.object.data["content"] == "hey"
506 assert cm_ref.unread == false
508 chat = Chat.get(recipient.id, author.ap_id)
510 [cm_ref] = MessageReference.for_chat_query(chat) |> Repo.all()
512 assert cm_ref.object.data["content"] == "hey"
513 assert cm_ref.unread == true
517 test "it creates a Chat for the local users and bumps the unread count" do
518 author = insert(:user, local: false)
519 recipient = insert(:user, local: true)
521 {:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey")
523 {:ok, create_activity_data, _meta} =
524 Builder.create(author, chat_message_data["id"], [recipient.ap_id])
526 {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false)
528 {:ok, _create_activity, _meta} =
529 SideEffects.handle(create_activity, local: false, object_data: chat_message_data)
531 # An object is created
532 assert Object.get_by_ap_id(chat_message_data["id"])
534 # The remote user won't get a chat
535 chat = Chat.get(author.id, recipient.ap_id)
538 # The local user will get a chat
539 chat = Chat.get(recipient.id, author.ap_id)
542 author = insert(:user, local: true)
543 recipient = insert(:user, local: true)
545 {:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey")
547 {:ok, create_activity_data, _meta} =
548 Builder.create(author, chat_message_data["id"], [recipient.ap_id])
550 {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false)
552 {:ok, _create_activity, _meta} =
553 SideEffects.handle(create_activity, local: false, object_data: chat_message_data)
555 # Both users are local and get the chat
556 chat = Chat.get(author.id, recipient.ap_id)
559 chat = Chat.get(recipient.id, author.ap_id)
564 describe "announce objects" do
566 poster = insert(:user)
568 {:ok, post} = CommonAPI.post(poster, %{status: "hey"})
569 {:ok, private_post} = CommonAPI.post(poster, %{status: "hey", visibility: "private"})
571 {:ok, announce_data, _meta} = Builder.announce(user, post.object, public: true)
573 {:ok, private_announce_data, _meta} =
574 Builder.announce(user, private_post.object, public: false)
576 {:ok, relay_announce_data, _meta} =
577 Builder.announce(Pleroma.Web.ActivityPub.Relay.get_actor(), post.object, public: true)
579 {:ok, announce, _meta} = ActivityPub.persist(announce_data, local: true)
580 {:ok, private_announce, _meta} = ActivityPub.persist(private_announce_data, local: true)
581 {:ok, relay_announce, _meta} = ActivityPub.persist(relay_announce_data, local: true)
587 private_announce: private_announce,
588 relay_announce: relay_announce
592 test "adds the announce to the original object", %{announce: announce, user: user} do
593 {:ok, announce, _} = SideEffects.handle(announce)
594 object = Object.get_by_ap_id(announce.data["object"])
595 assert object.data["announcement_count"] == 1
596 assert user.ap_id in object.data["announcements"]
599 test "does not add the announce to the original object if the actor is a service actor", %{
600 relay_announce: announce
602 {:ok, announce, _} = SideEffects.handle(announce)
603 object = Object.get_by_ap_id(announce.data["object"])
604 assert object.data["announcement_count"] == nil
607 test "creates a notification", %{announce: announce, poster: poster} do
608 {:ok, announce, _} = SideEffects.handle(announce)
609 assert Repo.get_by(Notification, user_id: poster.id, activity_id: announce.id)
612 test "it streams out the announce", %{announce: announce} do
615 Pleroma.Web.Streamer,
618 stream: fn _, _ -> nil end
625 send: fn _ -> nil end
629 {:ok, announce, _} = SideEffects.handle(announce)
632 Pleroma.Web.Streamer.stream(["user", "list", "public", "public:local"], announce)
635 assert called(Pleroma.Web.Push.send(:_))