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