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