1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.Web.ActivityPub.UtilsTest do
6 use Pleroma.DataCase, async: true
11 alias Pleroma.Web.ActivityPub.Utils
12 alias Pleroma.Web.AdminAPI.AccountView
13 alias Pleroma.Web.CommonAPI
15 import Pleroma.Factory
17 require Pleroma.Constants
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()
26 post_id = activity.data["id"]
33 account: target_account,
34 statuses: [%{"id" => post_id}],
42 |> Map.put("object", res["object"] ++ [nil, 1, 5, "123"])
44 {:ok, activity} = Pleroma.Web.ActivityPub.ActivityPub.insert(res)
46 [user_id, object | _] = activity.data["object"]
48 {:ok, stripped} = Utils.strip_report_status_data(activity)
50 assert stripped.data["object"] == [user_id, object["id"]]
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"])
60 assert activity == Utils.fetch_latest_follow(follower, followed)
64 describe "determine_explicit_mentions()" do
65 test "works with an object that has mentions" do
70 "href" => "https://example.com/~alyssa",
71 "name" => "Alyssa P. Hacker"
76 assert Utils.determine_explicit_mentions(object) == ["https://example.com/~alyssa"]
79 test "works with an object that does not have mentions" do
82 %{"type" => "Hashtag", "href" => "https://example.com/tag/2hu", "name" => "2hu"}
86 assert Utils.determine_explicit_mentions(object) == []
89 test "works with an object that has mentions and other tags" do
94 "href" => "https://example.com/~alyssa",
95 "name" => "Alyssa P. Hacker"
97 %{"type" => "Hashtag", "href" => "https://example.com/tag/2hu", "name" => "2hu"}
101 assert Utils.determine_explicit_mentions(object) == ["https://example.com/~alyssa"]
104 test "works with an object that has no tags" do
107 assert Utils.determine_explicit_mentions(object) == []
110 test "works with an object that has only IR tags" do
111 object = %{"tag" => ["2hu"]}
113 assert Utils.determine_explicit_mentions(object) == []
116 test "works with an object has tags as map" do
120 "href" => "https://example.com/~alyssa",
121 "name" => "Alyssa P. Hacker"
125 assert Utils.determine_explicit_mentions(object) == ["https://example.com/~alyssa"]
129 describe "make_like_data" do
132 other_user = insert(:user)
133 third_user = insert(:user)
134 [user: user, other_user: other_user, third_user: third_user]
137 test "addresses actor's follower address if the activity is public", %{
139 other_user: other_user,
140 third_user: third_user
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])
146 CommonAPI.post(user, %{
148 "hey @#{other_user.nickname}, @#{third_user.nickname} how about beering together this weekend?"
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
156 test "does not address actor's follower address if the activity is not public", %{
158 other_user: other_user,
159 third_user: third_user
161 expected_to = Enum.sort([user.ap_id])
162 expected_cc = [third_user.ap_id]
165 CommonAPI.post(user, %{
166 status: "@#{other_user.nickname} @#{third_user.nickname} bought a new swimsuit!",
167 visibility: "private"
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
176 test "make_json_ld_header/0" do
177 assert Utils.make_json_ld_header() == %{
179 "https://www.w3.org/ns/activitystreams",
180 "http://localhost:4001/schemas/litepub-0.1.jsonld",
188 describe "get_existing_votes" do
189 test "fetches existing votes" do
191 other_user = insert(:user)
194 CommonAPI.post(user, %{
195 status: "How do I pronounce LaTeX?",
197 options: ["laytekh", "lahtekh", "latex"],
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)
208 test "fetches only Create activities" do
210 other_user = insert(:user)
213 CommonAPI.post(user, %{
214 status: "Are we living in a society?",
216 options: ["yes", "no"],
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
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)
234 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
235 {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
238 follow_activity_two.data
239 |> Map.put("state", "accept")
241 cng = Ecto.Changeset.change(follow_activity_two, data: data)
243 {:ok, follow_activity_two} = Repo.update(cng)
245 {:ok, follow_activity_two} =
246 Utils.update_follow_state_for_all(follow_activity_two, "accept")
248 assert refresh_record(follow_activity).data["state"] == "accept"
249 assert refresh_record(follow_activity_two).data["state"] == "accept"
252 test "also updates the state of accepted follows" do
254 follower = insert(:user)
256 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
257 {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
259 {:ok, follow_activity_two} =
260 Utils.update_follow_state_for_all(follow_activity_two, "reject")
262 assert refresh_record(follow_activity).data["state"] == "reject"
263 assert refresh_record(follow_activity_two).data["state"] == "reject"
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)
272 {:ok, _, _, follow_activity} = CommonAPI.follow(follower, user)
273 {:ok, _, _, follow_activity_two} = CommonAPI.follow(follower, user)
276 follow_activity_two.data
277 |> Map.put("state", "accept")
279 cng = Ecto.Changeset.change(follow_activity_two, data: data)
281 {:ok, follow_activity_two} = Repo.update(cng)
283 {:ok, follow_activity_two} = Utils.update_follow_state(follow_activity_two, "reject")
285 assert refresh_record(follow_activity).data["state"] == "pending"
286 assert refresh_record(follow_activity_two).data["state"] == "reject"
290 describe "update_element_in_object/3" do
291 test "updates likes" do
293 activity = insert(:note_activity)
294 object = Object.normalize(activity, fetch: false)
296 assert {:ok, updated_object} =
297 Utils.update_element_in_object(
303 assert updated_object.data["likes"] == [user.ap_id]
304 assert updated_object.data["like_count"] == 1
308 describe "add_like_to_object/2" do
309 test "add actor to likes" do
311 user2 = insert(:user)
312 object = insert(:note)
314 assert {:ok, updated_object} =
315 Utils.add_like_to_object(
316 %Activity{data: %{"actor" => user.ap_id}},
320 assert updated_object.data["likes"] == [user.ap_id]
321 assert updated_object.data["like_count"] == 1
323 assert {:ok, updated_object2} =
324 Utils.add_like_to_object(
325 %Activity{data: %{"actor" => user2.ap_id}},
329 assert updated_object2.data["likes"] == [user2.ap_id, user.ap_id]
330 assert updated_object2.data["like_count"] == 2
334 describe "remove_like_from_object/2" do
335 test "removes ap_id from likes" do
337 user2 = insert(:user)
338 object = insert(:note, data: %{"likes" => [user.ap_id, user2.ap_id], "like_count" => 2})
340 assert {:ok, updated_object} =
341 Utils.remove_like_from_object(
342 %Activity{data: %{"actor" => user.ap_id}},
346 assert updated_object.data["likes"] == [user2.ap_id]
347 assert updated_object.data["like_count"] == 1
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)
357 refute Utils.get_existing_like(user.ap_id, object)
358 {:ok, like_activity} = CommonAPI.favorite(user, note_activity.id)
360 assert ^like_activity = Utils.get_existing_like(user.ap_id, object)
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"}})
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)
375 {:ok, announce} = CommonAPI.repeat(note_activity.id, actor)
376 assert Utils.get_existing_announce(actor.ap_id, object) == announce
380 describe "fetch_latest_block/2" do
381 test "fetches last block activities" do
382 user1 = insert(:user)
383 user2 = insert(:user)
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)
389 assert Utils.fetch_latest_block(user1, user2) == activity
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})
399 assert Utils.recipient_in_message(
402 %{"to" => [recipient.ap_id], "cc" => ""}
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})
411 assert Utils.recipient_in_message(
414 %{"cc" => [recipient.ap_id], "to" => ""}
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})
423 assert Utils.recipient_in_message(
426 %{"bcc" => "", "bto" => [recipient.ap_id]}
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})
435 assert Utils.recipient_in_message(
438 %{"bto" => "", "bcc" => [recipient.ap_id]}
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})
447 assert Utils.recipient_in_message(
450 %{"btod" => "", "bccc" => [recipient.ap_id]}
454 test "returns false" do
455 recipient = insert(:user)
456 actor = insert(:user)
457 refute Utils.recipient_in_message(recipient, actor, %{"to" => "ap_id"})
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"]
468 assert res["published"]
471 test "returns map with fake id and published data" do
473 "context" => "pleroma:fakecontext",
474 "id" => "pleroma:fakeid",
476 } = Utils.lazy_put_activity_defaults(%{}, true)
479 test "returns activity data with object" do
480 note_activity = insert(:note_activity)
481 object = Object.normalize(note_activity, fetch: false)
484 Utils.lazy_put_activity_defaults(%{
485 "context" => object.data["id"],
489 assert res["context"] == object.data["id"]
491 assert res["published"]
492 assert res["object"]["id"]
493 assert res["object"]["published"]
494 assert res["object"]["context"] == object.data["id"]
498 describe "make_flag_data" do
499 test "returns empty map when params is invalid" do
500 assert Utils.make_flag_data(%{}, %{}) == %{}
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()
510 target_ap_id = target_account.ap_id
511 object_ap_id = activity.object.data["id"]
514 Utils.make_flag_data(
518 account: target_account,
519 statuses: [%{"id" => activity.data["id"]}],
527 "id" => object_ap_id,
528 "content" => content,
529 "published" => activity.object.data["published"],
531 AccountView.render("show.json", %{user: target_account, skip_visibility_check: true})
536 "content" => ^content,
537 "context" => ^context,
538 "object" => [^target_ap_id, ^note_obj],
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)
548 {:ok, activity} = CommonAPI.post(posting_account, %{status: "foobar"})
549 {:ok, like} = CommonAPI.favorite(target_account, activity.id)
550 context = Utils.generate_context_id()
553 target_ap_id = target_account.ap_id
554 object_ap_id = activity.object.data["id"]
557 Utils.make_flag_data(
561 account: target_account,
562 statuses: [%{"id" => like.data["id"]}],
570 "id" => object_ap_id,
571 "content" => content,
572 "published" => activity.object.data["published"],
574 AccountView.render("show.json", %{user: posting_account, skip_visibility_check: true})
579 "content" => ^content,
580 "context" => ^context,
581 "object" => [^target_ap_id, ^note_obj],
587 describe "add_announce_to_object/2" do
588 test "adds actor to announcement" do
590 object = insert(:note)
593 insert(:note_activity,
595 "actor" => user.ap_id,
596 "cc" => [Pleroma.Constants.as_public()]
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
606 describe "remove_announce_from_object/2" do
607 test "removes actor from announcements" do
609 user2 = insert(:user)
613 data: %{"announcements" => [user.ap_id, user2.ap_id], "announcement_count" => 2}
616 activity = insert(:note_activity, data: %{"actor" => user.ap_id})
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
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) == []
629 object = insert(:note, data: %{"reactions" => [["x", ["lain"]]]})
630 assert Utils.get_cached_emoji_reactions(object) == [["x", ["lain"], nil]]
632 object = insert(:note, data: %{"reactions" => %{}})
633 assert Utils.get_cached_emoji_reactions(object) == []
637 describe "add_emoji_reaction_to_object/1" do
638 test "works with legacy 2-tuple format" do
640 other_user = insert(:user)
641 third_user = insert(:user)
647 "reactions" => [["😿", [other_user.ap_id]]]
651 _activity = insert(:note_activity, user: user, note: note)
653 Utils.add_emoji_reaction_to_object(
654 %Activity{data: %{"content" => "😿", "actor" => third_user.ap_id}},