total rebase
[anni] / test / pleroma / web / activity_pub / utils_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.ActivityPub.UtilsTest do
6   use Pleroma.DataCase, async: true
7   alias Pleroma.Activity
8   alias Pleroma.Object
9   alias Pleroma.Repo
10   alias Pleroma.User
11   alias Pleroma.Web.ActivityPub.Utils
12   alias Pleroma.Web.AdminAPI.AccountView
13   alias Pleroma.Web.CommonAPI
14
15   import Pleroma.Factory
16
17   require Pleroma.Constants
18
19   describe "strip_report_status_data/1" do
20     test "does not break on issues with the reported activities" do
21       reporter = insert(:user)
22       target_account = insert(:user)
23       {:ok, activity} = CommonAPI.post(target_account, %{status: "foobar"})
24       context = Utils.generate_context_id()
25       content = "foobar"
26       post_id = activity.data["id"]
27
28       res =
29         Utils.make_flag_data(
30           %{
31             actor: reporter,
32             context: context,
33             account: target_account,
34             statuses: [%{"id" => post_id}],
35             content: content
36           },
37           %{}
38         )
39
40       res =
41         res
42         |> Map.put("object", res["object"] ++ [nil, 1, 5, "123"])
43
44       {:ok, activity} = Pleroma.Web.ActivityPub.ActivityPub.insert(res)
45
46       [user_id, object | _] = activity.data["object"]
47
48       {:ok, stripped} = Utils.strip_report_status_data(activity)
49
50       assert stripped.data["object"] == [user_id, object["id"]]
51     end
52   end
53
54   describe "fetch the latest Follow" do
55     test "fetches the latest Follow activity" do
56       %Activity{data: %{"type" => "Follow"}} = activity = insert(:follow_activity)
57       follower = User.get_cached_by_ap_id(activity.data["actor"])
58       followed = User.get_cached_by_ap_id(activity.data["object"])
59
60       assert activity == Utils.fetch_latest_follow(follower, followed)
61     end
62   end
63
64   describe "determine_explicit_mentions()" do
65     test "works with an object that has mentions" do
66       object = %{
67         "tag" => [
68           %{
69             "type" => "Mention",
70             "href" => "https://example.com/~alyssa",
71             "name" => "Alyssa P. Hacker"
72           }
73         ]
74       }
75
76       assert Utils.determine_explicit_mentions(object) == ["https://example.com/~alyssa"]
77     end
78
79     test "works with an object that does not have mentions" do
80       object = %{
81         "tag" => [
82           %{"type" => "Hashtag", "href" => "https://example.com/tag/2hu", "name" => "2hu"}
83         ]
84       }
85
86       assert Utils.determine_explicit_mentions(object) == []
87     end
88
89     test "works with an object that has mentions and other tags" do
90       object = %{
91         "tag" => [
92           %{
93             "type" => "Mention",
94             "href" => "https://example.com/~alyssa",
95             "name" => "Alyssa P. Hacker"
96           },
97           %{"type" => "Hashtag", "href" => "https://example.com/tag/2hu", "name" => "2hu"}
98         ]
99       }
100
101       assert Utils.determine_explicit_mentions(object) == ["https://example.com/~alyssa"]
102     end
103
104     test "works with an object that has no tags" do
105       object = %{}
106
107       assert Utils.determine_explicit_mentions(object) == []
108     end
109
110     test "works with an object that has only IR tags" do
111       object = %{"tag" => ["2hu"]}
112
113       assert Utils.determine_explicit_mentions(object) == []
114     end
115
116     test "works with an object has tags as map" do
117       object = %{
118         "tag" => %{
119           "type" => "Mention",
120           "href" => "https://example.com/~alyssa",
121           "name" => "Alyssa P. Hacker"
122         }
123       }
124
125       assert Utils.determine_explicit_mentions(object) == ["https://example.com/~alyssa"]
126     end
127   end
128
129   describe "make_like_data" do
130     setup do
131       user = insert(:user)
132       other_user = insert(:user)
133       third_user = insert(:user)
134       [user: user, other_user: other_user, third_user: third_user]
135     end
136
137     test "addresses actor's follower address if the activity is public", %{
138       user: user,
139       other_user: other_user,
140       third_user: third_user
141     } do
142       expected_to = Enum.sort([user.ap_id, other_user.follower_address])
143       expected_cc = Enum.sort(["https://www.w3.org/ns/activitystreams#Public", third_user.ap_id])
144
145       {:ok, activity} =
146         CommonAPI.post(user, %{
147           status:
148             "hey @#{other_user.nickname}, @#{third_user.nickname} how about beering together this weekend?"
149         })
150
151       %{"to" => to, "cc" => cc} = Utils.make_like_data(other_user, activity, nil)
152       assert Enum.sort(to) == expected_to
153       assert Enum.sort(cc) == expected_cc
154     end
155
156     test "does not address actor's follower address if the activity is not public", %{
157       user: user,
158       other_user: other_user,
159       third_user: third_user
160     } do
161       expected_to = Enum.sort([user.ap_id])
162       expected_cc = [third_user.ap_id]
163
164       {:ok, activity} =
165         CommonAPI.post(user, %{
166           status: "@#{other_user.nickname} @#{third_user.nickname} bought a new swimsuit!",
167           visibility: "private"
168         })
169
170       %{"to" => to, "cc" => cc} = Utils.make_like_data(other_user, activity, nil)
171       assert Enum.sort(to) == expected_to
172       assert Enum.sort(cc) == expected_cc
173     end
174   end
175
176   test "make_json_ld_header/0" do
177     assert Utils.make_json_ld_header() == %{
178              "@context" => [
179                "https://www.w3.org/ns/activitystreams",
180                "http://localhost:4001/schemas/litepub-0.1.jsonld",
181                %{
182                  "@language" => "und"
183                }
184              ]
185            }
186   end
187
188   describe "get_existing_votes" do
189     test "fetches existing votes" do
190       user = insert(:user)
191       other_user = insert(:user)
192
193       {:ok, activity} =
194         CommonAPI.post(user, %{
195           status: "How do I pronounce LaTeX?",
196           poll: %{
197             options: ["laytekh", "lahtekh", "latex"],
198             expires_in: 20,
199             multiple: true
200           }
201         })
202
203       object = Object.normalize(activity, fetch: false)
204       {:ok, votes, object} = CommonAPI.vote(other_user, object, [0, 1])
205       assert Enum.sort(Utils.get_existing_votes(other_user.ap_id, object)) == Enum.sort(votes)
206     end
207
208     test "fetches only Create activities" do
209       user = insert(:user)
210       other_user = insert(:user)
211
212       {:ok, activity} =
213         CommonAPI.post(user, %{
214           status: "Are we living in a society?",
215           poll: %{
216             options: ["yes", "no"],
217             expires_in: 20
218           }
219         })
220
221       object = Object.normalize(activity, fetch: false)
222       {:ok, [vote], object} = CommonAPI.vote(other_user, object, [0])
223       {:ok, _activity} = CommonAPI.favorite(user, activity.id)
224       [fetched_vote] = Utils.get_existing_votes(other_user.ap_id, object)
225       assert fetched_vote.id == vote.id
226     end
227   end
228
229   describe "update_follow_state_for_all/2" do
230     test "updates the state of all Follow activities with the same actor and object" do
231       user = insert(:user, is_locked: true)
232       follower = insert(:user)
233
234       {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
235       {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
236
237       data =
238         follow_activity_two.data
239         |> Map.put("state", "accept")
240
241       cng = Ecto.Changeset.change(follow_activity_two, data: data)
242
243       {:ok, follow_activity_two} = Repo.update(cng)
244
245       {:ok, follow_activity_two} =
246         Utils.update_follow_state_for_all(follow_activity_two, "accept")
247
248       assert refresh_record(follow_activity).data["state"] == "accept"
249       assert refresh_record(follow_activity_two).data["state"] == "accept"
250     end
251
252     test "also updates the state of accepted follows" do
253       user = insert(:user)
254       follower = insert(:user)
255
256       {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
257       {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
258
259       {:ok, follow_activity_two} =
260         Utils.update_follow_state_for_all(follow_activity_two, "reject")
261
262       assert refresh_record(follow_activity).data["state"] == "reject"
263       assert refresh_record(follow_activity_two).data["state"] == "reject"
264     end
265   end
266
267   describe "update_follow_state/2" do
268     test "updates the state of the given follow activity" do
269       user = insert(:user, is_locked: true)
270       follower = insert(:user)
271
272       {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
273       {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
274
275       data =
276         follow_activity_two.data
277         |> Map.put("state", "accept")
278
279       cng = Ecto.Changeset.change(follow_activity_two, data: data)
280
281       {:ok, follow_activity_two} = Repo.update(cng)
282
283       {:ok, follow_activity_two} = Utils.update_follow_state(follow_activity_two, "reject")
284
285       assert refresh_record(follow_activity).data["state"] == "pending"
286       assert refresh_record(follow_activity_two).data["state"] == "reject"
287     end
288   end
289
290   describe "update_element_in_object/3" do
291     test "updates likes" do
292       user = insert(:user)
293       activity = insert(:note_activity)
294       object = Object.normalize(activity, fetch: false)
295
296       assert {:ok, updated_object} =
297                Utils.update_element_in_object(
298                  "like",
299                  [user.ap_id],
300                  object
301                )
302
303       assert updated_object.data["likes"] == [user.ap_id]
304       assert updated_object.data["like_count"] == 1
305     end
306   end
307
308   describe "add_like_to_object/2" do
309     test "add actor to likes" do
310       user = insert(:user)
311       user2 = insert(:user)
312       object = insert(:note)
313
314       assert {:ok, updated_object} =
315                Utils.add_like_to_object(
316                  %Activity{data: %{"actor" => user.ap_id}},
317                  object
318                )
319
320       assert updated_object.data["likes"] == [user.ap_id]
321       assert updated_object.data["like_count"] == 1
322
323       assert {:ok, updated_object2} =
324                Utils.add_like_to_object(
325                  %Activity{data: %{"actor" => user2.ap_id}},
326                  updated_object
327                )
328
329       assert updated_object2.data["likes"] == [user2.ap_id, user.ap_id]
330       assert updated_object2.data["like_count"] == 2
331     end
332   end
333
334   describe "remove_like_from_object/2" do
335     test "removes ap_id from likes" do
336       user = insert(:user)
337       user2 = insert(:user)
338       object = insert(:note, data: %{"likes" => [user.ap_id, user2.ap_id], "like_count" => 2})
339
340       assert {:ok, updated_object} =
341                Utils.remove_like_from_object(
342                  %Activity{data: %{"actor" => user.ap_id}},
343                  object
344                )
345
346       assert updated_object.data["likes"] == [user2.ap_id]
347       assert updated_object.data["like_count"] == 1
348     end
349   end
350
351   describe "get_existing_like/2" do
352     test "fetches existing like" do
353       note_activity = insert(:note_activity)
354       assert object = Object.normalize(note_activity, fetch: false)
355
356       user = insert(:user)
357       refute Utils.get_existing_like(user.ap_id, object)
358       {:ok, like_activity} = CommonAPI.favorite(user, note_activity.id)
359
360       assert ^like_activity = Utils.get_existing_like(user.ap_id, object)
361     end
362   end
363
364   describe "get_get_existing_announce/2" do
365     test "returns nil if announce not found" do
366       actor = insert(:user)
367       refute Utils.get_existing_announce(actor.ap_id, %{data: %{"id" => "test"}})
368     end
369
370     test "fetches existing announce" do
371       note_activity = insert(:note_activity)
372       assert object = Object.normalize(note_activity, fetch: false)
373       actor = insert(:user)
374
375       {:ok, announce} = CommonAPI.repeat(note_activity.id, actor)
376       assert Utils.get_existing_announce(actor.ap_id, object) == announce
377     end
378   end
379
380   describe "fetch_latest_block/2" do
381     test "fetches last block activities" do
382       user1 = insert(:user)
383       user2 = insert(:user)
384
385       assert {:ok, %Activity{} = _} = CommonAPI.block(user1, user2)
386       assert {:ok, %Activity{} = _} = CommonAPI.block(user1, user2)
387       assert {:ok, %Activity{} = activity} = CommonAPI.block(user1, user2)
388
389       assert Utils.fetch_latest_block(user1, user2) == activity
390     end
391   end
392
393   describe "recipient_in_message/3" do
394     test "returns true when recipient in `to`" do
395       recipient = insert(:user)
396       actor = insert(:user)
397       assert Utils.recipient_in_message(recipient, actor, %{"to" => recipient.ap_id})
398
399       assert Utils.recipient_in_message(
400                recipient,
401                actor,
402                %{"to" => [recipient.ap_id], "cc" => ""}
403              )
404     end
405
406     test "returns true when recipient in `cc`" do
407       recipient = insert(:user)
408       actor = insert(:user)
409       assert Utils.recipient_in_message(recipient, actor, %{"cc" => recipient.ap_id})
410
411       assert Utils.recipient_in_message(
412                recipient,
413                actor,
414                %{"cc" => [recipient.ap_id], "to" => ""}
415              )
416     end
417
418     test "returns true when recipient in `bto`" do
419       recipient = insert(:user)
420       actor = insert(:user)
421       assert Utils.recipient_in_message(recipient, actor, %{"bto" => recipient.ap_id})
422
423       assert Utils.recipient_in_message(
424                recipient,
425                actor,
426                %{"bcc" => "", "bto" => [recipient.ap_id]}
427              )
428     end
429
430     test "returns true when recipient in `bcc`" do
431       recipient = insert(:user)
432       actor = insert(:user)
433       assert Utils.recipient_in_message(recipient, actor, %{"bcc" => recipient.ap_id})
434
435       assert Utils.recipient_in_message(
436                recipient,
437                actor,
438                %{"bto" => "", "bcc" => [recipient.ap_id]}
439              )
440     end
441
442     test "returns true when message without addresses fields" do
443       recipient = insert(:user)
444       actor = insert(:user)
445       assert Utils.recipient_in_message(recipient, actor, %{"bccc" => recipient.ap_id})
446
447       assert Utils.recipient_in_message(
448                recipient,
449                actor,
450                %{"btod" => "", "bccc" => [recipient.ap_id]}
451              )
452     end
453
454     test "returns false" do
455       recipient = insert(:user)
456       actor = insert(:user)
457       refute Utils.recipient_in_message(recipient, actor, %{"to" => "ap_id"})
458     end
459   end
460
461   describe "lazy_put_activity_defaults/2" do
462     test "returns map with id and published data" do
463       note_activity = insert(:note_activity)
464       object = Object.normalize(note_activity, fetch: false)
465       res = Utils.lazy_put_activity_defaults(%{"context" => object.data["id"]})
466       assert res["context"] == object.data["id"]
467       assert res["id"]
468       assert res["published"]
469     end
470
471     test "returns map with fake id and published data" do
472       assert %{
473                "context" => "pleroma:fakecontext",
474                "id" => "pleroma:fakeid",
475                "published" => _
476              } = Utils.lazy_put_activity_defaults(%{}, true)
477     end
478
479     test "returns activity data with object" do
480       note_activity = insert(:note_activity)
481       object = Object.normalize(note_activity, fetch: false)
482
483       res =
484         Utils.lazy_put_activity_defaults(%{
485           "context" => object.data["id"],
486           "object" => %{}
487         })
488
489       assert res["context"] == object.data["id"]
490       assert res["id"]
491       assert res["published"]
492       assert res["object"]["id"]
493       assert res["object"]["published"]
494       assert res["object"]["context"] == object.data["id"]
495     end
496   end
497
498   describe "make_flag_data" do
499     test "returns empty map when params is invalid" do
500       assert Utils.make_flag_data(%{}, %{}) == %{}
501     end
502
503     test "returns map with Flag object" do
504       reporter = insert(:user)
505       target_account = insert(:user)
506       {:ok, activity} = CommonAPI.post(target_account, %{status: "foobar"})
507       context = Utils.generate_context_id()
508       content = "foobar"
509
510       target_ap_id = target_account.ap_id
511       object_ap_id = activity.object.data["id"]
512
513       res =
514         Utils.make_flag_data(
515           %{
516             actor: reporter,
517             context: context,
518             account: target_account,
519             statuses: [%{"id" => activity.data["id"]}],
520             content: content
521           },
522           %{}
523         )
524
525       note_obj = %{
526         "type" => "Note",
527         "id" => object_ap_id,
528         "content" => content,
529         "published" => activity.object.data["published"],
530         "actor" =>
531           AccountView.render("show.json", %{user: target_account, skip_visibility_check: true})
532       }
533
534       assert %{
535                "type" => "Flag",
536                "content" => ^content,
537                "context" => ^context,
538                "object" => [^target_ap_id, ^note_obj],
539                "state" => "open"
540              } = res
541     end
542
543     test "returns map with Flag object with a non-Create Activity" do
544       reporter = insert(:user)
545       posting_account = insert(:user)
546       target_account = insert(:user)
547
548       {:ok, activity} = CommonAPI.post(posting_account, %{status: "foobar"})
549       {:ok, like} = CommonAPI.favorite(target_account, activity.id)
550       context = Utils.generate_context_id()
551       content = "foobar"
552
553       target_ap_id = target_account.ap_id
554       object_ap_id = activity.object.data["id"]
555
556       res =
557         Utils.make_flag_data(
558           %{
559             actor: reporter,
560             context: context,
561             account: target_account,
562             statuses: [%{"id" => like.data["id"]}],
563             content: content
564           },
565           %{}
566         )
567
568       note_obj = %{
569         "type" => "Note",
570         "id" => object_ap_id,
571         "content" => content,
572         "published" => activity.object.data["published"],
573         "actor" =>
574           AccountView.render("show.json", %{user: posting_account, skip_visibility_check: true})
575       }
576
577       assert %{
578                "type" => "Flag",
579                "content" => ^content,
580                "context" => ^context,
581                "object" => [^target_ap_id, ^note_obj],
582                "state" => "open"
583              } = res
584     end
585   end
586
587   describe "add_announce_to_object/2" do
588     test "adds actor to announcement" do
589       user = insert(:user)
590       object = insert(:note)
591
592       activity =
593         insert(:note_activity,
594           data: %{
595             "actor" => user.ap_id,
596             "cc" => [Pleroma.Constants.as_public()]
597           }
598         )
599
600       assert {:ok, updated_object} = Utils.add_announce_to_object(activity, object)
601       assert updated_object.data["announcements"] == [user.ap_id]
602       assert updated_object.data["announcement_count"] == 1
603     end
604   end
605
606   describe "remove_announce_from_object/2" do
607     test "removes actor from announcements" do
608       user = insert(:user)
609       user2 = insert(:user)
610
611       object =
612         insert(:note,
613           data: %{"announcements" => [user.ap_id, user2.ap_id], "announcement_count" => 2}
614         )
615
616       activity = insert(:note_activity, data: %{"actor" => user.ap_id})
617
618       assert {:ok, updated_object} = Utils.remove_announce_from_object(activity, object)
619       assert updated_object.data["announcements"] == [user2.ap_id]
620       assert updated_object.data["announcement_count"] == 1
621     end
622   end
623
624   describe "get_cached_emoji_reactions/1" do
625     test "returns the normalized data or an empty list" do
626       object = insert(:note)
627       assert Utils.get_cached_emoji_reactions(object) == []
628
629       object = insert(:note, data: %{"reactions" => [["x", ["lain"]]]})
630       assert Utils.get_cached_emoji_reactions(object) == [["x", ["lain"], nil]]
631
632       object = insert(:note, data: %{"reactions" => %{}})
633       assert Utils.get_cached_emoji_reactions(object) == []
634     end
635   end
636
637   describe "add_emoji_reaction_to_object/1" do
638     test "works with legacy 2-tuple format" do
639       user = insert(:user)
640       other_user = insert(:user)
641       third_user = insert(:user)
642
643       note =
644         insert(:note,
645           user: user,
646           data: %{
647             "reactions" => [["😿", [other_user.ap_id]]]
648           }
649         )
650
651       _activity = insert(:note_activity, user: user, note: note)
652
653       Utils.add_emoji_reaction_to_object(
654         %Activity{data: %{"content" => "😿", "actor" => third_user.ap_id}},
655         note
656       )
657     end
658   end
659 end