Check if mogrify available before calling it
[akkoma] / test / web / activity_pub / side_effects_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.ActivityPub.SideEffectsTest do
6 use Oban.Testing, repo: Pleroma.Repo
7 use Pleroma.DataCase
8
9 alias Pleroma.Activity
10 alias Pleroma.Chat
11 alias Pleroma.Chat.MessageReference
12 alias Pleroma.Notification
13 alias Pleroma.Object
14 alias Pleroma.Repo
15 alias Pleroma.Tests.ObanHelpers
16 alias Pleroma.User
17 alias Pleroma.Web.ActivityPub.ActivityPub
18 alias Pleroma.Web.ActivityPub.Builder
19 alias Pleroma.Web.ActivityPub.SideEffects
20 alias Pleroma.Web.CommonAPI
21
22 import Pleroma.Factory
23 import Mock
24
25 describe "handle_after_transaction" do
26 test "it streams out notifications and streams" do
27 author = insert(:user, local: true)
28 recipient = insert(:user, local: true)
29
30 {:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey")
31
32 {:ok, create_activity_data, _meta} =
33 Builder.create(author, chat_message_data["id"], [recipient.ap_id])
34
35 {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false)
36
37 {:ok, _create_activity, meta} =
38 SideEffects.handle(create_activity, local: false, object_data: chat_message_data)
39
40 assert [notification] = meta[:notifications]
41
42 with_mocks([
43 {
44 Pleroma.Web.Streamer,
45 [],
46 [
47 stream: fn _, _ -> nil end
48 ]
49 },
50 {
51 Pleroma.Web.Push,
52 [],
53 [
54 send: fn _ -> nil end
55 ]
56 }
57 ]) do
58 SideEffects.handle_after_transaction(meta)
59
60 assert called(Pleroma.Web.Streamer.stream(["user", "user:notification"], notification))
61 assert called(Pleroma.Web.Streamer.stream(["user", "user:pleroma_chat"], :_))
62 assert called(Pleroma.Web.Push.send(notification))
63 end
64 end
65 end
66
67 describe "blocking users" do
68 setup do
69 user = insert(:user)
70 blocked = insert(:user)
71 User.follow(blocked, user)
72 User.follow(user, blocked)
73
74 {:ok, block_data, []} = Builder.block(user, blocked)
75 {:ok, block, _meta} = ActivityPub.persist(block_data, local: true)
76
77 %{user: user, blocked: blocked, block: block}
78 end
79
80 test "it unfollows and blocks", %{user: user, blocked: blocked, block: block} do
81 assert User.following?(user, blocked)
82 assert User.following?(blocked, user)
83
84 {:ok, _, _} = SideEffects.handle(block)
85
86 refute User.following?(user, blocked)
87 refute User.following?(blocked, user)
88 assert User.blocks?(user, blocked)
89 end
90
91 test "it blocks but does not unfollow if the relevant setting is set", %{
92 user: user,
93 blocked: blocked,
94 block: block
95 } do
96 clear_config([:activitypub, :unfollow_blocked], false)
97 assert User.following?(user, blocked)
98 assert User.following?(blocked, user)
99
100 {:ok, _, _} = SideEffects.handle(block)
101
102 refute User.following?(user, blocked)
103 assert User.following?(blocked, user)
104 assert User.blocks?(user, blocked)
105 end
106 end
107
108 describe "update users" do
109 setup do
110 user = insert(:user)
111 {:ok, update_data, []} = Builder.update(user, %{"id" => user.ap_id, "name" => "new name!"})
112 {:ok, update, _meta} = ActivityPub.persist(update_data, local: true)
113
114 %{user: user, update_data: update_data, update: update}
115 end
116
117 test "it updates the user", %{user: user, update: update} do
118 {:ok, _, _} = SideEffects.handle(update)
119 user = User.get_by_id(user.id)
120 assert user.name == "new name!"
121 end
122
123 test "it uses a given changeset to update", %{user: user, update: update} do
124 changeset = Ecto.Changeset.change(user, %{default_scope: "direct"})
125
126 assert user.default_scope == "public"
127 {:ok, _, _} = SideEffects.handle(update, user_update_changeset: changeset)
128 user = User.get_by_id(user.id)
129 assert user.default_scope == "direct"
130 end
131 end
132
133 describe "delete objects" do
134 setup do
135 user = insert(:user)
136 other_user = insert(:user)
137
138 {:ok, op} = CommonAPI.post(other_user, %{status: "big oof"})
139 {:ok, post} = CommonAPI.post(user, %{status: "hey", in_reply_to_id: op})
140 {:ok, favorite} = CommonAPI.favorite(user, post.id)
141 object = Object.normalize(post)
142 {:ok, delete_data, _meta} = Builder.delete(user, object.data["id"])
143 {:ok, delete_user_data, _meta} = Builder.delete(user, user.ap_id)
144 {:ok, delete, _meta} = ActivityPub.persist(delete_data, local: true)
145 {:ok, delete_user, _meta} = ActivityPub.persist(delete_user_data, local: true)
146
147 %{
148 user: user,
149 delete: delete,
150 post: post,
151 object: object,
152 delete_user: delete_user,
153 op: op,
154 favorite: favorite
155 }
156 end
157
158 test "it handles object deletions", %{
159 delete: delete,
160 post: post,
161 object: object,
162 user: user,
163 op: op,
164 favorite: favorite
165 } do
166 with_mock Pleroma.Web.ActivityPub.ActivityPub, [:passthrough],
167 stream_out: fn _ -> nil end,
168 stream_out_participations: fn _, _ -> nil end do
169 {:ok, delete, _} = SideEffects.handle(delete)
170 user = User.get_cached_by_ap_id(object.data["actor"])
171
172 assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out(delete))
173 assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out_participations(object, user))
174 end
175
176 object = Object.get_by_id(object.id)
177 assert object.data["type"] == "Tombstone"
178 refute Activity.get_by_id(post.id)
179 refute Activity.get_by_id(favorite.id)
180
181 user = User.get_by_id(user.id)
182 assert user.note_count == 0
183
184 object = Object.normalize(op.data["object"], false)
185
186 assert object.data["repliesCount"] == 0
187 end
188
189 test "it handles object deletions when the object itself has been pruned", %{
190 delete: delete,
191 post: post,
192 object: object,
193 user: user,
194 op: op
195 } do
196 with_mock Pleroma.Web.ActivityPub.ActivityPub, [:passthrough],
197 stream_out: fn _ -> nil end,
198 stream_out_participations: fn _, _ -> nil end do
199 {:ok, delete, _} = SideEffects.handle(delete)
200 user = User.get_cached_by_ap_id(object.data["actor"])
201
202 assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out(delete))
203 assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out_participations(object, user))
204 end
205
206 object = Object.get_by_id(object.id)
207 assert object.data["type"] == "Tombstone"
208 refute Activity.get_by_id(post.id)
209
210 user = User.get_by_id(user.id)
211 assert user.note_count == 0
212
213 object = Object.normalize(op.data["object"], false)
214
215 assert object.data["repliesCount"] == 0
216 end
217
218 test "it handles user deletions", %{delete_user: delete, user: user} do
219 {:ok, _delete, _} = SideEffects.handle(delete)
220 ObanHelpers.perform_all()
221
222 assert User.get_cached_by_ap_id(user.ap_id).deactivated
223 end
224 end
225
226 describe "EmojiReact objects" do
227 setup do
228 poster = insert(:user)
229 user = insert(:user)
230
231 {:ok, post} = CommonAPI.post(poster, %{status: "hey"})
232
233 {:ok, emoji_react_data, []} = Builder.emoji_react(user, post.object, "👌")
234 {:ok, emoji_react, _meta} = ActivityPub.persist(emoji_react_data, local: true)
235
236 %{emoji_react: emoji_react, user: user, poster: poster}
237 end
238
239 test "adds the reaction to the object", %{emoji_react: emoji_react, user: user} do
240 {:ok, emoji_react, _} = SideEffects.handle(emoji_react)
241 object = Object.get_by_ap_id(emoji_react.data["object"])
242
243 assert object.data["reaction_count"] == 1
244 assert ["👌", [user.ap_id]] in object.data["reactions"]
245 end
246
247 test "creates a notification", %{emoji_react: emoji_react, poster: poster} do
248 {:ok, emoji_react, _} = SideEffects.handle(emoji_react)
249 assert Repo.get_by(Notification, user_id: poster.id, activity_id: emoji_react.id)
250 end
251 end
252
253 describe "delete users with confirmation pending" do
254 setup do
255 user = insert(:user, confirmation_pending: true)
256 {:ok, delete_user_data, _meta} = Builder.delete(user, user.ap_id)
257 {:ok, delete_user, _meta} = ActivityPub.persist(delete_user_data, local: true)
258 {:ok, delete: delete_user, user: user}
259 end
260
261 test "when activation is not required", %{delete: delete, user: user} do
262 clear_config([:instance, :account_activation_required], false)
263 {:ok, _, _} = SideEffects.handle(delete)
264 ObanHelpers.perform_all()
265
266 assert User.get_cached_by_id(user.id).deactivated
267 end
268
269 test "when activation is required", %{delete: delete, user: user} do
270 clear_config([:instance, :account_activation_required], true)
271 {:ok, _, _} = SideEffects.handle(delete)
272 ObanHelpers.perform_all()
273
274 refute User.get_cached_by_id(user.id)
275 end
276 end
277
278 describe "Undo objects" do
279 setup do
280 poster = insert(:user)
281 user = insert(:user)
282 {:ok, post} = CommonAPI.post(poster, %{status: "hey"})
283 {:ok, like} = CommonAPI.favorite(user, post.id)
284 {:ok, reaction} = CommonAPI.react_with_emoji(post.id, user, "👍")
285 {:ok, announce} = CommonAPI.repeat(post.id, user)
286 {:ok, block} = CommonAPI.block(user, poster)
287
288 {:ok, undo_data, _meta} = Builder.undo(user, like)
289 {:ok, like_undo, _meta} = ActivityPub.persist(undo_data, local: true)
290
291 {:ok, undo_data, _meta} = Builder.undo(user, reaction)
292 {:ok, reaction_undo, _meta} = ActivityPub.persist(undo_data, local: true)
293
294 {:ok, undo_data, _meta} = Builder.undo(user, announce)
295 {:ok, announce_undo, _meta} = ActivityPub.persist(undo_data, local: true)
296
297 {:ok, undo_data, _meta} = Builder.undo(user, block)
298 {:ok, block_undo, _meta} = ActivityPub.persist(undo_data, local: true)
299
300 %{
301 like_undo: like_undo,
302 post: post,
303 like: like,
304 reaction_undo: reaction_undo,
305 reaction: reaction,
306 announce_undo: announce_undo,
307 announce: announce,
308 block_undo: block_undo,
309 block: block,
310 poster: poster,
311 user: user
312 }
313 end
314
315 test "deletes the original block", %{block_undo: block_undo, block: block} do
316 {:ok, _block_undo, _} = SideEffects.handle(block_undo)
317 refute Activity.get_by_id(block.id)
318 end
319
320 test "unblocks the blocked user", %{block_undo: block_undo, block: block} do
321 blocker = User.get_by_ap_id(block.data["actor"])
322 blocked = User.get_by_ap_id(block.data["object"])
323
324 {:ok, _block_undo, _} = SideEffects.handle(block_undo)
325 refute User.blocks?(blocker, blocked)
326 end
327
328 test "an announce undo removes the announce from the object", %{
329 announce_undo: announce_undo,
330 post: post
331 } do
332 {:ok, _announce_undo, _} = SideEffects.handle(announce_undo)
333
334 object = Object.get_by_ap_id(post.data["object"])
335
336 assert object.data["announcement_count"] == 0
337 assert object.data["announcements"] == []
338 end
339
340 test "deletes the original announce", %{announce_undo: announce_undo, announce: announce} do
341 {:ok, _announce_undo, _} = SideEffects.handle(announce_undo)
342 refute Activity.get_by_id(announce.id)
343 end
344
345 test "a reaction undo removes the reaction from the object", %{
346 reaction_undo: reaction_undo,
347 post: post
348 } do
349 {:ok, _reaction_undo, _} = SideEffects.handle(reaction_undo)
350
351 object = Object.get_by_ap_id(post.data["object"])
352
353 assert object.data["reaction_count"] == 0
354 assert object.data["reactions"] == []
355 end
356
357 test "deletes the original reaction", %{reaction_undo: reaction_undo, reaction: reaction} do
358 {:ok, _reaction_undo, _} = SideEffects.handle(reaction_undo)
359 refute Activity.get_by_id(reaction.id)
360 end
361
362 test "a like undo removes the like from the object", %{like_undo: like_undo, post: post} do
363 {:ok, _like_undo, _} = SideEffects.handle(like_undo)
364
365 object = Object.get_by_ap_id(post.data["object"])
366
367 assert object.data["like_count"] == 0
368 assert object.data["likes"] == []
369 end
370
371 test "deletes the original like", %{like_undo: like_undo, like: like} do
372 {:ok, _like_undo, _} = SideEffects.handle(like_undo)
373 refute Activity.get_by_id(like.id)
374 end
375 end
376
377 describe "like objects" do
378 setup do
379 poster = insert(:user)
380 user = insert(:user)
381 {:ok, post} = CommonAPI.post(poster, %{status: "hey"})
382
383 {:ok, like_data, _meta} = Builder.like(user, post.object)
384 {:ok, like, _meta} = ActivityPub.persist(like_data, local: true)
385
386 %{like: like, user: user, poster: poster}
387 end
388
389 test "add the like to the original object", %{like: like, user: user} do
390 {:ok, like, _} = SideEffects.handle(like)
391 object = Object.get_by_ap_id(like.data["object"])
392 assert object.data["like_count"] == 1
393 assert user.ap_id in object.data["likes"]
394 end
395
396 test "creates a notification", %{like: like, poster: poster} do
397 {:ok, like, _} = SideEffects.handle(like)
398 assert Repo.get_by(Notification, user_id: poster.id, activity_id: like.id)
399 end
400 end
401
402 describe "creation of ChatMessages" do
403 test "notifies the recipient" do
404 author = insert(:user, local: false)
405 recipient = insert(:user, local: true)
406
407 {:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey")
408
409 {:ok, create_activity_data, _meta} =
410 Builder.create(author, chat_message_data["id"], [recipient.ap_id])
411
412 {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false)
413
414 {:ok, _create_activity, _meta} =
415 SideEffects.handle(create_activity, local: false, object_data: chat_message_data)
416
417 assert Repo.get_by(Notification, user_id: recipient.id, activity_id: create_activity.id)
418 end
419
420 test "it streams the created ChatMessage" do
421 author = insert(:user, local: true)
422 recipient = insert(:user, local: true)
423
424 {:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey")
425
426 {:ok, create_activity_data, _meta} =
427 Builder.create(author, chat_message_data["id"], [recipient.ap_id])
428
429 {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false)
430
431 {:ok, _create_activity, meta} =
432 SideEffects.handle(create_activity, local: false, object_data: chat_message_data)
433
434 assert [_, _] = meta[:streamables]
435 end
436
437 test "it creates a Chat and MessageReferences for the local users and bumps the unread count, except for the author" do
438 author = insert(:user, local: true)
439 recipient = insert(:user, local: true)
440
441 {:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey")
442
443 {:ok, create_activity_data, _meta} =
444 Builder.create(author, chat_message_data["id"], [recipient.ap_id])
445
446 {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false)
447
448 with_mocks([
449 {
450 Pleroma.Web.Streamer,
451 [],
452 [
453 stream: fn _, _ -> nil end
454 ]
455 },
456 {
457 Pleroma.Web.Push,
458 [],
459 [
460 send: fn _ -> nil end
461 ]
462 }
463 ]) do
464 {:ok, _create_activity, meta} =
465 SideEffects.handle(create_activity, local: false, object_data: chat_message_data)
466
467 # The notification gets created
468 assert [notification] = meta[:notifications]
469 assert notification.activity_id == create_activity.id
470
471 # But it is not sent out
472 refute called(Pleroma.Web.Streamer.stream(["user", "user:notification"], notification))
473 refute called(Pleroma.Web.Push.send(notification))
474
475 # Same for the user chat stream
476 assert [{topics, _}, _] = meta[:streamables]
477 assert topics == ["user", "user:pleroma_chat"]
478 refute called(Pleroma.Web.Streamer.stream(["user", "user:pleroma_chat"], :_))
479
480 chat = Chat.get(author.id, recipient.ap_id)
481
482 [cm_ref] = MessageReference.for_chat_query(chat) |> Repo.all()
483
484 assert cm_ref.object.data["content"] == "hey"
485 assert cm_ref.unread == false
486
487 chat = Chat.get(recipient.id, author.ap_id)
488
489 [cm_ref] = MessageReference.for_chat_query(chat) |> Repo.all()
490
491 assert cm_ref.object.data["content"] == "hey"
492 assert cm_ref.unread == true
493 end
494 end
495
496 test "it creates a Chat for the local users and bumps the unread count" do
497 author = insert(:user, local: false)
498 recipient = insert(:user, local: true)
499
500 {:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey")
501
502 {:ok, create_activity_data, _meta} =
503 Builder.create(author, chat_message_data["id"], [recipient.ap_id])
504
505 {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false)
506
507 {:ok, _create_activity, _meta} =
508 SideEffects.handle(create_activity, local: false, object_data: chat_message_data)
509
510 # An object is created
511 assert Object.get_by_ap_id(chat_message_data["id"])
512
513 # The remote user won't get a chat
514 chat = Chat.get(author.id, recipient.ap_id)
515 refute chat
516
517 # The local user will get a chat
518 chat = Chat.get(recipient.id, author.ap_id)
519 assert chat
520
521 author = insert(:user, local: true)
522 recipient = insert(:user, local: true)
523
524 {:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey")
525
526 {:ok, create_activity_data, _meta} =
527 Builder.create(author, chat_message_data["id"], [recipient.ap_id])
528
529 {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false)
530
531 {:ok, _create_activity, _meta} =
532 SideEffects.handle(create_activity, local: false, object_data: chat_message_data)
533
534 # Both users are local and get the chat
535 chat = Chat.get(author.id, recipient.ap_id)
536 assert chat
537
538 chat = Chat.get(recipient.id, author.ap_id)
539 assert chat
540 end
541 end
542
543 describe "announce objects" do
544 setup do
545 poster = insert(:user)
546 user = insert(:user)
547 {:ok, post} = CommonAPI.post(poster, %{status: "hey"})
548 {:ok, private_post} = CommonAPI.post(poster, %{status: "hey", visibility: "private"})
549
550 {:ok, announce_data, _meta} = Builder.announce(user, post.object, public: true)
551
552 {:ok, private_announce_data, _meta} =
553 Builder.announce(user, private_post.object, public: false)
554
555 {:ok, relay_announce_data, _meta} =
556 Builder.announce(Pleroma.Web.ActivityPub.Relay.get_actor(), post.object, public: true)
557
558 {:ok, announce, _meta} = ActivityPub.persist(announce_data, local: true)
559 {:ok, private_announce, _meta} = ActivityPub.persist(private_announce_data, local: true)
560 {:ok, relay_announce, _meta} = ActivityPub.persist(relay_announce_data, local: true)
561
562 %{
563 announce: announce,
564 user: user,
565 poster: poster,
566 private_announce: private_announce,
567 relay_announce: relay_announce
568 }
569 end
570
571 test "adds the announce to the original object", %{announce: announce, user: user} do
572 {:ok, announce, _} = SideEffects.handle(announce)
573 object = Object.get_by_ap_id(announce.data["object"])
574 assert object.data["announcement_count"] == 1
575 assert user.ap_id in object.data["announcements"]
576 end
577
578 test "does not add the announce to the original object if the actor is a service actor", %{
579 relay_announce: announce
580 } do
581 {:ok, announce, _} = SideEffects.handle(announce)
582 object = Object.get_by_ap_id(announce.data["object"])
583 assert object.data["announcement_count"] == nil
584 end
585
586 test "creates a notification", %{announce: announce, poster: poster} do
587 {:ok, announce, _} = SideEffects.handle(announce)
588 assert Repo.get_by(Notification, user_id: poster.id, activity_id: announce.id)
589 end
590
591 test "it streams out the announce", %{announce: announce} do
592 with_mocks([
593 {
594 Pleroma.Web.Streamer,
595 [],
596 [
597 stream: fn _, _ -> nil end
598 ]
599 },
600 {
601 Pleroma.Web.Push,
602 [],
603 [
604 send: fn _ -> nil end
605 ]
606 }
607 ]) do
608 {:ok, announce, _} = SideEffects.handle(announce)
609
610 assert called(
611 Pleroma.Web.Streamer.stream(["user", "list", "public", "public:local"], announce)
612 )
613
614 assert called(Pleroma.Web.Push.send(:_))
615 end
616 end
617 end
618 end