Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into embedded-object...
[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, but sets `embedded_object`", %{
316 block_undo: block_undo,
317 block: block
318 } do
319 {:ok, _block_undo, meta} = SideEffects.handle(block_undo)
320
321 assert meta[:embedded_object] == block.data
322 refute Activity.get_by_id(block.id)
323 end
324
325 test "unblocks the blocked user", %{block_undo: block_undo, block: block} do
326 blocker = User.get_by_ap_id(block.data["actor"])
327 blocked = User.get_by_ap_id(block.data["object"])
328
329 {:ok, _block_undo, _} = SideEffects.handle(block_undo)
330 refute User.blocks?(blocker, blocked)
331 end
332
333 test "an announce undo removes the announce from the object", %{
334 announce_undo: announce_undo,
335 post: post
336 } do
337 {:ok, _announce_undo, _} = SideEffects.handle(announce_undo)
338
339 object = Object.get_by_ap_id(post.data["object"])
340
341 assert object.data["announcement_count"] == 0
342 assert object.data["announcements"] == []
343 end
344
345 test "deletes the original announce", %{announce_undo: announce_undo, announce: announce} do
346 {:ok, _announce_undo, _} = SideEffects.handle(announce_undo)
347 refute Activity.get_by_id(announce.id)
348 end
349
350 test "a reaction undo removes the reaction from the object", %{
351 reaction_undo: reaction_undo,
352 post: post
353 } do
354 {:ok, _reaction_undo, _} = SideEffects.handle(reaction_undo)
355
356 object = Object.get_by_ap_id(post.data["object"])
357
358 assert object.data["reaction_count"] == 0
359 assert object.data["reactions"] == []
360 end
361
362 test "deletes the original reaction", %{reaction_undo: reaction_undo, reaction: reaction} do
363 {:ok, _reaction_undo, _} = SideEffects.handle(reaction_undo)
364 refute Activity.get_by_id(reaction.id)
365 end
366
367 test "a like undo removes the like from the object", %{like_undo: like_undo, post: post} do
368 {:ok, _like_undo, _} = SideEffects.handle(like_undo)
369
370 object = Object.get_by_ap_id(post.data["object"])
371
372 assert object.data["like_count"] == 0
373 assert object.data["likes"] == []
374 end
375
376 test "deletes the original like", %{like_undo: like_undo, like: like} do
377 {:ok, _like_undo, _} = SideEffects.handle(like_undo)
378 refute Activity.get_by_id(like.id)
379 end
380 end
381
382 describe "like objects" do
383 setup do
384 poster = insert(:user)
385 user = insert(:user)
386 {:ok, post} = CommonAPI.post(poster, %{status: "hey"})
387
388 {:ok, like_data, _meta} = Builder.like(user, post.object)
389 {:ok, like, _meta} = ActivityPub.persist(like_data, local: true)
390
391 %{like: like, user: user, poster: poster}
392 end
393
394 test "add the like to the original object", %{like: like, user: user} do
395 {:ok, like, _} = SideEffects.handle(like)
396 object = Object.get_by_ap_id(like.data["object"])
397 assert object.data["like_count"] == 1
398 assert user.ap_id in object.data["likes"]
399 end
400
401 test "creates a notification", %{like: like, poster: poster} do
402 {:ok, like, _} = SideEffects.handle(like)
403 assert Repo.get_by(Notification, user_id: poster.id, activity_id: like.id)
404 end
405 end
406
407 describe "creation of ChatMessages" do
408 test "notifies the recipient" do
409 author = insert(:user, local: false)
410 recipient = insert(:user, local: true)
411
412 {:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey")
413
414 {:ok, create_activity_data, _meta} =
415 Builder.create(author, chat_message_data["id"], [recipient.ap_id])
416
417 {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false)
418
419 {:ok, _create_activity, _meta} =
420 SideEffects.handle(create_activity, local: false, object_data: chat_message_data)
421
422 assert Repo.get_by(Notification, user_id: recipient.id, activity_id: create_activity.id)
423 end
424
425 test "it streams the created ChatMessage" do
426 author = insert(:user, local: true)
427 recipient = insert(:user, local: true)
428
429 {:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey")
430
431 {:ok, create_activity_data, _meta} =
432 Builder.create(author, chat_message_data["id"], [recipient.ap_id])
433
434 {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false)
435
436 {:ok, _create_activity, meta} =
437 SideEffects.handle(create_activity, local: false, object_data: chat_message_data)
438
439 assert [_, _] = meta[:streamables]
440 end
441
442 test "it creates a Chat and MessageReferences for the local users and bumps the unread count, except for the author" do
443 author = insert(:user, local: true)
444 recipient = insert(:user, local: true)
445
446 {:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey")
447
448 {:ok, create_activity_data, _meta} =
449 Builder.create(author, chat_message_data["id"], [recipient.ap_id])
450
451 {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false)
452
453 with_mocks([
454 {
455 Pleroma.Web.Streamer,
456 [],
457 [
458 stream: fn _, _ -> nil end
459 ]
460 },
461 {
462 Pleroma.Web.Push,
463 [],
464 [
465 send: fn _ -> nil end
466 ]
467 }
468 ]) do
469 {:ok, _create_activity, meta} =
470 SideEffects.handle(create_activity, local: false, object_data: chat_message_data)
471
472 # The notification gets created
473 assert [notification] = meta[:notifications]
474 assert notification.activity_id == create_activity.id
475
476 # But it is not sent out
477 refute called(Pleroma.Web.Streamer.stream(["user", "user:notification"], notification))
478 refute called(Pleroma.Web.Push.send(notification))
479
480 # Same for the user chat stream
481 assert [{topics, _}, _] = meta[:streamables]
482 assert topics == ["user", "user:pleroma_chat"]
483 refute called(Pleroma.Web.Streamer.stream(["user", "user:pleroma_chat"], :_))
484
485 chat = Chat.get(author.id, recipient.ap_id)
486
487 [cm_ref] = MessageReference.for_chat_query(chat) |> Repo.all()
488
489 assert cm_ref.object.data["content"] == "hey"
490 assert cm_ref.unread == false
491
492 chat = Chat.get(recipient.id, author.ap_id)
493
494 [cm_ref] = MessageReference.for_chat_query(chat) |> Repo.all()
495
496 assert cm_ref.object.data["content"] == "hey"
497 assert cm_ref.unread == true
498 end
499 end
500
501 test "it creates a Chat for the local users and bumps the unread count" do
502 author = insert(:user, local: false)
503 recipient = insert(:user, local: true)
504
505 {:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey")
506
507 {:ok, create_activity_data, _meta} =
508 Builder.create(author, chat_message_data["id"], [recipient.ap_id])
509
510 {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false)
511
512 {:ok, _create_activity, _meta} =
513 SideEffects.handle(create_activity, local: false, object_data: chat_message_data)
514
515 # An object is created
516 assert Object.get_by_ap_id(chat_message_data["id"])
517
518 # The remote user won't get a chat
519 chat = Chat.get(author.id, recipient.ap_id)
520 refute chat
521
522 # The local user will get a chat
523 chat = Chat.get(recipient.id, author.ap_id)
524 assert chat
525
526 author = insert(:user, local: true)
527 recipient = insert(:user, local: true)
528
529 {:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey")
530
531 {:ok, create_activity_data, _meta} =
532 Builder.create(author, chat_message_data["id"], [recipient.ap_id])
533
534 {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false)
535
536 {:ok, _create_activity, _meta} =
537 SideEffects.handle(create_activity, local: false, object_data: chat_message_data)
538
539 # Both users are local and get the chat
540 chat = Chat.get(author.id, recipient.ap_id)
541 assert chat
542
543 chat = Chat.get(recipient.id, author.ap_id)
544 assert chat
545 end
546 end
547
548 describe "announce objects" do
549 setup do
550 poster = insert(:user)
551 user = insert(:user)
552 {:ok, post} = CommonAPI.post(poster, %{status: "hey"})
553 {:ok, private_post} = CommonAPI.post(poster, %{status: "hey", visibility: "private"})
554
555 {:ok, announce_data, _meta} = Builder.announce(user, post.object, public: true)
556
557 {:ok, private_announce_data, _meta} =
558 Builder.announce(user, private_post.object, public: false)
559
560 {:ok, relay_announce_data, _meta} =
561 Builder.announce(Pleroma.Web.ActivityPub.Relay.get_actor(), post.object, public: true)
562
563 {:ok, announce, _meta} = ActivityPub.persist(announce_data, local: true)
564 {:ok, private_announce, _meta} = ActivityPub.persist(private_announce_data, local: true)
565 {:ok, relay_announce, _meta} = ActivityPub.persist(relay_announce_data, local: true)
566
567 %{
568 announce: announce,
569 user: user,
570 poster: poster,
571 private_announce: private_announce,
572 relay_announce: relay_announce
573 }
574 end
575
576 test "adds the announce to the original object", %{announce: announce, user: user} do
577 {:ok, announce, _} = SideEffects.handle(announce)
578 object = Object.get_by_ap_id(announce.data["object"])
579 assert object.data["announcement_count"] == 1
580 assert user.ap_id in object.data["announcements"]
581 end
582
583 test "does not add the announce to the original object if the actor is a service actor", %{
584 relay_announce: announce
585 } do
586 {:ok, announce, _} = SideEffects.handle(announce)
587 object = Object.get_by_ap_id(announce.data["object"])
588 assert object.data["announcement_count"] == nil
589 end
590
591 test "creates a notification", %{announce: announce, poster: poster} do
592 {:ok, announce, _} = SideEffects.handle(announce)
593 assert Repo.get_by(Notification, user_id: poster.id, activity_id: announce.id)
594 end
595
596 test "it streams out the announce", %{announce: announce} do
597 with_mocks([
598 {
599 Pleroma.Web.Streamer,
600 [],
601 [
602 stream: fn _, _ -> nil end
603 ]
604 },
605 {
606 Pleroma.Web.Push,
607 [],
608 [
609 send: fn _ -> nil end
610 ]
611 }
612 ]) do
613 {:ok, announce, _} = SideEffects.handle(announce)
614
615 assert called(
616 Pleroma.Web.Streamer.stream(["user", "list", "public", "public:local"], announce)
617 )
618
619 assert called(Pleroma.Web.Push.send(:_))
620 end
621 end
622 end
623 end