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