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