Chats: Remove `unread` from the db, calculate from unseen messages.
[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 object = Object.normalize(create_activity, false)
329
330 assert called(Pleroma.Web.Streamer.stream(["user", "user:pleroma_chat"], object))
331 end
332 end
333
334 test "it creates a Chat and ChatMessageReferences for the local users and bumps the unread count, except for the author" do
335 author = insert(:user, local: true)
336 recipient = insert(:user, local: true)
337
338 {:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey")
339
340 {:ok, create_activity_data, _meta} =
341 Builder.create(author, chat_message_data["id"], [recipient.ap_id])
342
343 {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false)
344
345 {:ok, _create_activity, _meta} =
346 SideEffects.handle(create_activity, local: false, object_data: chat_message_data)
347
348 chat = Chat.get(author.id, recipient.ap_id)
349
350 [cm_ref] = ChatMessageReference.for_chat_query(chat) |> Repo.all()
351
352 assert cm_ref.object.data["content"] == "hey"
353 assert cm_ref.seen == true
354
355 chat = Chat.get(recipient.id, author.ap_id)
356
357 [cm_ref] = ChatMessageReference.for_chat_query(chat) |> Repo.all()
358
359 assert cm_ref.object.data["content"] == "hey"
360 assert cm_ref.seen == false
361 end
362
363 test "it creates a Chat for the local users and bumps the unread count" 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 # An object is created
378 assert Object.get_by_ap_id(chat_message_data["id"])
379
380 # The remote user won't get a chat
381 chat = Chat.get(author.id, recipient.ap_id)
382 refute chat
383
384 # The local user will get a chat
385 chat = Chat.get(recipient.id, author.ap_id)
386 assert chat
387
388 author = insert(:user, local: true)
389 recipient = insert(:user, local: true)
390
391 {:ok, chat_message_data, _meta} = Builder.chat_message(author, recipient.ap_id, "hey")
392
393 {:ok, create_activity_data, _meta} =
394 Builder.create(author, chat_message_data["id"], [recipient.ap_id])
395
396 {:ok, create_activity, _meta} = ActivityPub.persist(create_activity_data, local: false)
397
398 {:ok, _create_activity, _meta} =
399 SideEffects.handle(create_activity, local: false, object_data: chat_message_data)
400
401 # Both users are local and get the chat
402 chat = Chat.get(author.id, recipient.ap_id)
403 assert chat
404
405 chat = Chat.get(recipient.id, author.ap_id)
406 assert chat
407 end
408 end
409
410 describe "announce objects" do
411 setup do
412 poster = insert(:user)
413 user = insert(:user)
414 {:ok, post} = CommonAPI.post(poster, %{status: "hey"})
415 {:ok, private_post} = CommonAPI.post(poster, %{status: "hey", visibility: "private"})
416
417 {:ok, announce_data, _meta} = Builder.announce(user, post.object, public: true)
418
419 {:ok, private_announce_data, _meta} =
420 Builder.announce(user, private_post.object, public: false)
421
422 {:ok, relay_announce_data, _meta} =
423 Builder.announce(Pleroma.Web.ActivityPub.Relay.get_actor(), post.object, public: true)
424
425 {:ok, announce, _meta} = ActivityPub.persist(announce_data, local: true)
426 {:ok, private_announce, _meta} = ActivityPub.persist(private_announce_data, local: true)
427 {:ok, relay_announce, _meta} = ActivityPub.persist(relay_announce_data, local: true)
428
429 %{
430 announce: announce,
431 user: user,
432 poster: poster,
433 private_announce: private_announce,
434 relay_announce: relay_announce
435 }
436 end
437
438 test "adds the announce to the original object", %{announce: announce, user: user} do
439 {:ok, announce, _} = SideEffects.handle(announce)
440 object = Object.get_by_ap_id(announce.data["object"])
441 assert object.data["announcement_count"] == 1
442 assert user.ap_id in object.data["announcements"]
443 end
444
445 test "does not add the announce to the original object if the actor is a service actor", %{
446 relay_announce: announce
447 } do
448 {:ok, announce, _} = SideEffects.handle(announce)
449 object = Object.get_by_ap_id(announce.data["object"])
450 assert object.data["announcement_count"] == nil
451 end
452
453 test "creates a notification", %{announce: announce, poster: poster} do
454 {:ok, announce, _} = SideEffects.handle(announce)
455 assert Repo.get_by(Notification, user_id: poster.id, activity_id: announce.id)
456 end
457
458 test "it streams out the announce", %{announce: announce} do
459 with_mock Pleroma.Web.ActivityPub.ActivityPub, [:passthrough], stream_out: fn _ -> nil end do
460 {:ok, announce, _} = SideEffects.handle(announce)
461
462 assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out(announce))
463 end
464 end
465 end
466 end