Pipeline: Add a side effects step after the transaction finishes
[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.ChatMessageReference
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" 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[:created_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.Push.send(notification))
62 end
63 end
64 end
65
66 describe "delete objects" do
67 setup do
68 user = insert(:user)
69 other_user = insert(:user)
70
71 {:ok, op} = CommonAPI.post(other_user, %{status: "big oof"})
72 {:ok, post} = CommonAPI.post(user, %{status: "hey", in_reply_to_id: op})
73 {:ok, favorite} = CommonAPI.favorite(user, post.id)
74 object = Object.normalize(post)
75 {:ok, delete_data, _meta} = Builder.delete(user, object.data["id"])
76 {:ok, delete_user_data, _meta} = Builder.delete(user, user.ap_id)
77 {:ok, delete, _meta} = ActivityPub.persist(delete_data, local: true)
78 {:ok, delete_user, _meta} = ActivityPub.persist(delete_user_data, local: true)
79
80 %{
81 user: user,
82 delete: delete,
83 post: post,
84 object: object,
85 delete_user: delete_user,
86 op: op,
87 favorite: favorite
88 }
89 end
90
91 test "it handles object deletions", %{
92 delete: delete,
93 post: post,
94 object: object,
95 user: user,
96 op: op,
97 favorite: favorite
98 } do
99 with_mock Pleroma.Web.ActivityPub.ActivityPub, [:passthrough],
100 stream_out: fn _ -> nil end,
101 stream_out_participations: fn _, _ -> nil end do
102 {:ok, delete, _} = SideEffects.handle(delete)
103 user = User.get_cached_by_ap_id(object.data["actor"])
104
105 assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out(delete))
106 assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out_participations(object, user))
107 end
108
109 object = Object.get_by_id(object.id)
110 assert object.data["type"] == "Tombstone"
111 refute Activity.get_by_id(post.id)
112 refute Activity.get_by_id(favorite.id)
113
114 user = User.get_by_id(user.id)
115 assert user.note_count == 0
116
117 object = Object.normalize(op.data["object"], false)
118
119 assert object.data["repliesCount"] == 0
120 end
121
122 test "it handles object deletions when the object itself has been pruned", %{
123 delete: delete,
124 post: post,
125 object: object,
126 user: user,
127 op: op
128 } do
129 with_mock Pleroma.Web.ActivityPub.ActivityPub, [:passthrough],
130 stream_out: fn _ -> nil end,
131 stream_out_participations: fn _, _ -> nil end do
132 {:ok, delete, _} = SideEffects.handle(delete)
133 user = User.get_cached_by_ap_id(object.data["actor"])
134
135 assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out(delete))
136 assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out_participations(object, user))
137 end
138
139 object = Object.get_by_id(object.id)
140 assert object.data["type"] == "Tombstone"
141 refute Activity.get_by_id(post.id)
142
143 user = User.get_by_id(user.id)
144 assert user.note_count == 0
145
146 object = Object.normalize(op.data["object"], false)
147
148 assert object.data["repliesCount"] == 0
149 end
150
151 test "it handles user deletions", %{delete_user: delete, user: user} do
152 {:ok, _delete, _} = SideEffects.handle(delete)
153 ObanHelpers.perform_all()
154
155 assert User.get_cached_by_ap_id(user.ap_id).deactivated
156 end
157 end
158
159 describe "EmojiReact objects" do
160 setup do
161 poster = insert(:user)
162 user = insert(:user)
163
164 {:ok, post} = CommonAPI.post(poster, %{status: "hey"})
165
166 {:ok, emoji_react_data, []} = Builder.emoji_react(user, post.object, "👌")
167 {:ok, emoji_react, _meta} = ActivityPub.persist(emoji_react_data, local: true)
168
169 %{emoji_react: emoji_react, user: user, poster: poster}
170 end
171
172 test "adds the reaction to the object", %{emoji_react: emoji_react, user: user} do
173 {:ok, emoji_react, _} = SideEffects.handle(emoji_react)
174 object = Object.get_by_ap_id(emoji_react.data["object"])
175
176 assert object.data["reaction_count"] == 1
177 assert ["👌", [user.ap_id]] in object.data["reactions"]
178 end
179
180 test "creates a notification", %{emoji_react: emoji_react, poster: poster} do
181 {:ok, emoji_react, _} = SideEffects.handle(emoji_react)
182 assert Repo.get_by(Notification, user_id: poster.id, activity_id: emoji_react.id)
183 end
184 end
185
186 describe "delete users with confirmation pending" do
187 setup do
188 user = insert(:user, confirmation_pending: true)
189 {:ok, delete_user_data, _meta} = Builder.delete(user, user.ap_id)
190 {:ok, delete_user, _meta} = ActivityPub.persist(delete_user_data, local: true)
191 {:ok, delete: delete_user, user: user}
192 end
193
194 test "when activation is not required", %{delete: delete, user: user} do
195 clear_config([:instance, :account_activation_required], false)
196 {:ok, _, _} = SideEffects.handle(delete)
197 ObanHelpers.perform_all()
198
199 assert User.get_cached_by_id(user.id).deactivated
200 end
201
202 test "when activation is required", %{delete: delete, user: user} do
203 clear_config([:instance, :account_activation_required], true)
204 {:ok, _, _} = SideEffects.handle(delete)
205 ObanHelpers.perform_all()
206
207 refute User.get_cached_by_id(user.id)
208 end
209 end
210
211 describe "Undo objects" do
212 setup do
213 poster = insert(:user)
214 user = insert(:user)
215 {:ok, post} = CommonAPI.post(poster, %{status: "hey"})
216 {:ok, like} = CommonAPI.favorite(user, post.id)
217 {:ok, reaction} = CommonAPI.react_with_emoji(post.id, user, "👍")
218 {:ok, announce} = CommonAPI.repeat(post.id, user)
219 {:ok, block} = ActivityPub.block(user, poster)
220 User.block(user, poster)
221
222 {:ok, undo_data, _meta} = Builder.undo(user, like)
223 {:ok, like_undo, _meta} = ActivityPub.persist(undo_data, local: true)
224
225 {:ok, undo_data, _meta} = Builder.undo(user, reaction)
226 {:ok, reaction_undo, _meta} = ActivityPub.persist(undo_data, local: true)
227
228 {:ok, undo_data, _meta} = Builder.undo(user, announce)
229 {:ok, announce_undo, _meta} = ActivityPub.persist(undo_data, local: true)
230
231 {:ok, undo_data, _meta} = Builder.undo(user, block)
232 {:ok, block_undo, _meta} = ActivityPub.persist(undo_data, local: true)
233
234 %{
235 like_undo: like_undo,
236 post: post,
237 like: like,
238 reaction_undo: reaction_undo,
239 reaction: reaction,
240 announce_undo: announce_undo,
241 announce: announce,
242 block_undo: block_undo,
243 block: block,
244 poster: poster,
245 user: user
246 }
247 end
248
249 test "deletes the original block", %{block_undo: block_undo, block: block} do
250 {:ok, _block_undo, _} = SideEffects.handle(block_undo)
251 refute Activity.get_by_id(block.id)
252 end
253
254 test "unblocks the blocked user", %{block_undo: block_undo, block: block} do
255 blocker = User.get_by_ap_id(block.data["actor"])
256 blocked = User.get_by_ap_id(block.data["object"])
257
258 {:ok, _block_undo, _} = SideEffects.handle(block_undo)
259 refute User.blocks?(blocker, blocked)
260 end
261
262 test "an announce undo removes the announce from the object", %{
263 announce_undo: announce_undo,
264 post: post
265 } do
266 {:ok, _announce_undo, _} = SideEffects.handle(announce_undo)
267
268 object = Object.get_by_ap_id(post.data["object"])
269
270 assert object.data["announcement_count"] == 0
271 assert object.data["announcements"] == []
272 end
273
274 test "deletes the original announce", %{announce_undo: announce_undo, announce: announce} do
275 {:ok, _announce_undo, _} = SideEffects.handle(announce_undo)
276 refute Activity.get_by_id(announce.id)
277 end
278
279 test "a reaction undo removes the reaction from the object", %{
280 reaction_undo: reaction_undo,
281 post: post
282 } do
283 {:ok, _reaction_undo, _} = SideEffects.handle(reaction_undo)
284
285 object = Object.get_by_ap_id(post.data["object"])
286
287 assert object.data["reaction_count"] == 0
288 assert object.data["reactions"] == []
289 end
290
291 test "deletes the original reaction", %{reaction_undo: reaction_undo, reaction: reaction} do
292 {:ok, _reaction_undo, _} = SideEffects.handle(reaction_undo)
293 refute Activity.get_by_id(reaction.id)
294 end
295
296 test "a like undo removes the like from the object", %{like_undo: like_undo, post: post} do
297 {:ok, _like_undo, _} = SideEffects.handle(like_undo)
298
299 object = Object.get_by_ap_id(post.data["object"])
300
301 assert object.data["like_count"] == 0
302 assert object.data["likes"] == []
303 end
304
305 test "deletes the original like", %{like_undo: like_undo, like: like} do
306 {:ok, _like_undo, _} = SideEffects.handle(like_undo)
307 refute Activity.get_by_id(like.id)
308 end
309 end
310
311 describe "like objects" do
312 setup do
313 poster = insert(:user)
314 user = insert(:user)
315 {:ok, post} = CommonAPI.post(poster, %{status: "hey"})
316
317 {:ok, like_data, _meta} = Builder.like(user, post.object)
318 {:ok, like, _meta} = ActivityPub.persist(like_data, local: true)
319
320 %{like: like, user: user, poster: poster}
321 end
322
323 test "add the like to the original object", %{like: like, user: user} do
324 {:ok, like, _} = SideEffects.handle(like)
325 object = Object.get_by_ap_id(like.data["object"])
326 assert object.data["like_count"] == 1
327 assert user.ap_id in object.data["likes"]
328 end
329
330 test "creates a notification", %{like: like, poster: poster} do
331 {:ok, like, _} = SideEffects.handle(like)
332 assert Repo.get_by(Notification, user_id: poster.id, activity_id: like.id)
333 end
334 end
335
336 describe "creation of ChatMessages" do
337 test "notifies the recipient" do
338 author = insert(:user, local: false)
339 recipient = insert(:user, local: true)
340
341 {:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey")
342
343 {:ok, create_activity_data, _meta} =
344 Builder.create(author, chat_message_data["id"], [recipient.ap_id])
345
346 {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false)
347
348 {:ok, _create_activity, _meta} =
349 SideEffects.handle(create_activity, local: false, object_data: chat_message_data)
350
351 assert Repo.get_by(Notification, user_id: recipient.id, activity_id: create_activity.id)
352 end
353
354 test "it streams the created ChatMessage" do
355 author = insert(:user, local: true)
356 recipient = insert(:user, local: true)
357
358 {:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey")
359
360 {:ok, create_activity_data, _meta} =
361 Builder.create(author, chat_message_data["id"], [recipient.ap_id])
362
363 {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false)
364
365 with_mock Pleroma.Web.Streamer, [],
366 stream: fn _, payload ->
367 case payload do
368 {^author, cm_ref} ->
369 assert cm_ref.unread == false
370
371 {^recipient, cm_ref} ->
372 assert cm_ref.unread == true
373
374 view =
375 Pleroma.Web.PleromaAPI.ChatView.render("show.json",
376 last_message: cm_ref,
377 chat: cm_ref.chat
378 )
379
380 assert view.unread == 1
381
382 _ ->
383 nil
384 end
385 end do
386 {:ok, _create_activity, _meta} =
387 SideEffects.handle(create_activity, local: false, object_data: chat_message_data)
388
389 assert called(Pleroma.Web.Streamer.stream(["user", "user:pleroma_chat"], {author, :_}))
390 assert called(Pleroma.Web.Streamer.stream(["user", "user:pleroma_chat"], {recipient, :_}))
391 end
392 end
393
394 test "it creates a Chat and ChatMessageReferences for the local users and bumps the unread count, except for the author" do
395 author = insert(:user, local: true)
396 recipient = insert(:user, local: true)
397
398 {:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey")
399
400 {:ok, create_activity_data, _meta} =
401 Builder.create(author, chat_message_data["id"], [recipient.ap_id])
402
403 {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false)
404
405 with_mocks([
406 {
407 Pleroma.Web.Streamer,
408 [],
409 [
410 stream: fn _, _ -> nil end
411 ]
412 },
413 {
414 Pleroma.Web.Push,
415 [],
416 [
417 send: fn _ -> nil end
418 ]
419 }
420 ]) do
421 {:ok, _create_activity, meta} =
422 SideEffects.handle(create_activity, local: false, object_data: chat_message_data)
423
424 # The notification gets created
425 assert [notification] = meta[:created_notifications]
426 assert notification.activity_id == create_activity.id
427
428 # But it is not sent out
429 refute called(Pleroma.Web.Streamer.stream(["user", "user:notification"], notification))
430 refute called(Pleroma.Web.Push.send(notification))
431
432 chat = Chat.get(author.id, recipient.ap_id)
433
434 [cm_ref] = ChatMessageReference.for_chat_query(chat) |> Repo.all()
435
436 assert cm_ref.object.data["content"] == "hey"
437 assert cm_ref.unread == false
438
439 chat = Chat.get(recipient.id, author.ap_id)
440
441 [cm_ref] = ChatMessageReference.for_chat_query(chat) |> Repo.all()
442
443 assert cm_ref.object.data["content"] == "hey"
444 assert cm_ref.unread == true
445 end
446 end
447
448 test "it creates a Chat for the local users and bumps the unread count" do
449 author = insert(:user, local: false)
450 recipient = insert(:user, local: true)
451
452 {:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey")
453
454 {:ok, create_activity_data, _meta} =
455 Builder.create(author, chat_message_data["id"], [recipient.ap_id])
456
457 {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false)
458
459 {:ok, _create_activity, _meta} =
460 SideEffects.handle(create_activity, local: false, object_data: chat_message_data)
461
462 # An object is created
463 assert Object.get_by_ap_id(chat_message_data["id"])
464
465 # The remote user won't get a chat
466 chat = Chat.get(author.id, recipient.ap_id)
467 refute chat
468
469 # The local user will get a chat
470 chat = Chat.get(recipient.id, author.ap_id)
471 assert chat
472
473 author = insert(:user, local: true)
474 recipient = insert(:user, local: true)
475
476 {:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey")
477
478 {:ok, create_activity_data, _meta} =
479 Builder.create(author, chat_message_data["id"], [recipient.ap_id])
480
481 {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false)
482
483 {:ok, _create_activity, _meta} =
484 SideEffects.handle(create_activity, local: false, object_data: chat_message_data)
485
486 # Both users are local and get the chat
487 chat = Chat.get(author.id, recipient.ap_id)
488 assert chat
489
490 chat = Chat.get(recipient.id, author.ap_id)
491 assert chat
492 end
493 end
494
495 describe "announce objects" do
496 setup do
497 poster = insert(:user)
498 user = insert(:user)
499 {:ok, post} = CommonAPI.post(poster, %{status: "hey"})
500 {:ok, private_post} = CommonAPI.post(poster, %{status: "hey", visibility: "private"})
501
502 {:ok, announce_data, _meta} = Builder.announce(user, post.object, public: true)
503
504 {:ok, private_announce_data, _meta} =
505 Builder.announce(user, private_post.object, public: false)
506
507 {:ok, relay_announce_data, _meta} =
508 Builder.announce(Pleroma.Web.ActivityPub.Relay.get_actor(), post.object, public: true)
509
510 {:ok, announce, _meta} = ActivityPub.persist(announce_data, local: true)
511 {:ok, private_announce, _meta} = ActivityPub.persist(private_announce_data, local: true)
512 {:ok, relay_announce, _meta} = ActivityPub.persist(relay_announce_data, local: true)
513
514 %{
515 announce: announce,
516 user: user,
517 poster: poster,
518 private_announce: private_announce,
519 relay_announce: relay_announce
520 }
521 end
522
523 test "adds the announce to the original object", %{announce: announce, user: user} do
524 {:ok, announce, _} = SideEffects.handle(announce)
525 object = Object.get_by_ap_id(announce.data["object"])
526 assert object.data["announcement_count"] == 1
527 assert user.ap_id in object.data["announcements"]
528 end
529
530 test "does not add the announce to the original object if the actor is a service actor", %{
531 relay_announce: announce
532 } do
533 {:ok, announce, _} = SideEffects.handle(announce)
534 object = Object.get_by_ap_id(announce.data["object"])
535 assert object.data["announcement_count"] == nil
536 end
537
538 test "creates a notification", %{announce: announce, poster: poster} do
539 {:ok, announce, _} = SideEffects.handle(announce)
540 assert Repo.get_by(Notification, user_id: poster.id, activity_id: announce.id)
541 end
542
543 test "it streams out the announce", %{announce: announce} do
544 with_mock Pleroma.Web.ActivityPub.ActivityPub, [:passthrough], stream_out: fn _ -> nil end do
545 {:ok, announce, _} = SideEffects.handle(announce)
546
547 assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out(announce))
548 end
549 end
550 end
551 end