Merge branch 'develop' into feature/hide-follows-remote
[akkoma] / test / web / activity_pub / activity_pub_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
6 use Pleroma.DataCase
7 alias Pleroma.Activity
8 alias Pleroma.Builders.ActivityBuilder
9 alias Pleroma.Object
10 alias Pleroma.User
11 alias Pleroma.Web.ActivityPub.ActivityPub
12 alias Pleroma.Web.ActivityPub.Utils
13 alias Pleroma.Web.CommonAPI
14
15 import Pleroma.Factory
16 import Tesla.Mock
17 import Mock
18
19 setup do
20 mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
21 :ok
22 end
23
24 describe "streaming out participations" do
25 test "it streams them out" do
26 user = insert(:user)
27 {:ok, activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"})
28
29 {:ok, conversation} = Pleroma.Conversation.create_or_bump_for(activity)
30
31 participations =
32 conversation.participations
33 |> Repo.preload(:user)
34
35 with_mock Pleroma.Web.Streamer,
36 stream: fn _, _ -> nil end do
37 ActivityPub.stream_out_participations(conversation.participations)
38
39 Enum.each(participations, fn participation ->
40 assert called(Pleroma.Web.Streamer.stream("participation", participation))
41 end)
42 end
43 end
44 end
45
46 describe "fetching restricted by visibility" do
47 test "it restricts by the appropriate visibility" do
48 user = insert(:user)
49
50 {:ok, public_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "public"})
51
52 {:ok, direct_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"})
53
54 {:ok, unlisted_activity} =
55 CommonAPI.post(user, %{"status" => ".", "visibility" => "unlisted"})
56
57 {:ok, private_activity} =
58 CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
59
60 activities =
61 ActivityPub.fetch_activities([], %{:visibility => "direct", "actor_id" => user.ap_id})
62
63 assert activities == [direct_activity]
64
65 activities =
66 ActivityPub.fetch_activities([], %{:visibility => "unlisted", "actor_id" => user.ap_id})
67
68 assert activities == [unlisted_activity]
69
70 activities =
71 ActivityPub.fetch_activities([], %{:visibility => "private", "actor_id" => user.ap_id})
72
73 assert activities == [private_activity]
74
75 activities =
76 ActivityPub.fetch_activities([], %{:visibility => "public", "actor_id" => user.ap_id})
77
78 assert activities == [public_activity]
79
80 activities =
81 ActivityPub.fetch_activities([], %{
82 :visibility => ~w[private public],
83 "actor_id" => user.ap_id
84 })
85
86 assert activities == [public_activity, private_activity]
87 end
88 end
89
90 describe "building a user from his ap id" do
91 test "it returns a user" do
92 user_id = "http://mastodon.example.org/users/admin"
93 {:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
94 assert user.ap_id == user_id
95 assert user.nickname == "admin@mastodon.example.org"
96 assert user.info.source_data
97 assert user.info.ap_enabled
98 assert user.follower_address == "http://mastodon.example.org/users/admin/followers"
99 end
100
101 test "it fetches the appropriate tag-restricted posts" do
102 user = insert(:user)
103
104 {:ok, status_one} = CommonAPI.post(user, %{"status" => ". #test"})
105 {:ok, status_two} = CommonAPI.post(user, %{"status" => ". #essais"})
106 {:ok, status_three} = CommonAPI.post(user, %{"status" => ". #test #reject"})
107
108 fetch_one = ActivityPub.fetch_activities([], %{"type" => "Create", "tag" => "test"})
109
110 fetch_two =
111 ActivityPub.fetch_activities([], %{"type" => "Create", "tag" => ["test", "essais"]})
112
113 fetch_three =
114 ActivityPub.fetch_activities([], %{
115 "type" => "Create",
116 "tag" => ["test", "essais"],
117 "tag_reject" => ["reject"]
118 })
119
120 fetch_four =
121 ActivityPub.fetch_activities([], %{
122 "type" => "Create",
123 "tag" => ["test"],
124 "tag_all" => ["test", "reject"]
125 })
126
127 assert fetch_one == [status_one, status_three]
128 assert fetch_two == [status_one, status_two, status_three]
129 assert fetch_three == [status_one, status_two]
130 assert fetch_four == [status_three]
131 end
132 end
133
134 describe "insertion" do
135 test "drops activities beyond a certain limit" do
136 limit = Pleroma.Config.get([:instance, :remote_limit])
137
138 random_text =
139 :crypto.strong_rand_bytes(limit + 1)
140 |> Base.encode64()
141 |> binary_part(0, limit + 1)
142
143 data = %{
144 "ok" => true,
145 "object" => %{
146 "content" => random_text
147 }
148 }
149
150 assert {:error, {:remote_limit_error, _}} = ActivityPub.insert(data)
151 end
152
153 test "doesn't drop activities with content being null" do
154 user = insert(:user)
155
156 data = %{
157 "actor" => user.ap_id,
158 "to" => [],
159 "object" => %{
160 "actor" => user.ap_id,
161 "to" => [],
162 "type" => "Note",
163 "content" => nil
164 }
165 }
166
167 assert {:ok, _} = ActivityPub.insert(data)
168 end
169
170 test "returns the activity if one with the same id is already in" do
171 activity = insert(:note_activity)
172 {:ok, new_activity} = ActivityPub.insert(activity.data)
173
174 assert activity.id == new_activity.id
175 end
176
177 test "inserts a given map into the activity database, giving it an id if it has none." do
178 user = insert(:user)
179
180 data = %{
181 "actor" => user.ap_id,
182 "to" => [],
183 "object" => %{
184 "actor" => user.ap_id,
185 "to" => [],
186 "type" => "Note",
187 "content" => "hey"
188 }
189 }
190
191 {:ok, %Activity{} = activity} = ActivityPub.insert(data)
192 assert activity.data["ok"] == data["ok"]
193 assert is_binary(activity.data["id"])
194
195 given_id = "bla"
196
197 data = %{
198 "id" => given_id,
199 "actor" => user.ap_id,
200 "to" => [],
201 "context" => "blabla",
202 "object" => %{
203 "actor" => user.ap_id,
204 "to" => [],
205 "type" => "Note",
206 "content" => "hey"
207 }
208 }
209
210 {:ok, %Activity{} = activity} = ActivityPub.insert(data)
211 assert activity.data["ok"] == data["ok"]
212 assert activity.data["id"] == given_id
213 assert activity.data["context"] == "blabla"
214 assert activity.data["context_id"]
215 end
216
217 test "adds a context when none is there" do
218 user = insert(:user)
219
220 data = %{
221 "actor" => user.ap_id,
222 "to" => [],
223 "object" => %{
224 "actor" => user.ap_id,
225 "to" => [],
226 "type" => "Note",
227 "content" => "hey"
228 }
229 }
230
231 {:ok, %Activity{} = activity} = ActivityPub.insert(data)
232 object = Pleroma.Object.normalize(activity)
233
234 assert is_binary(activity.data["context"])
235 assert is_binary(object.data["context"])
236 assert activity.data["context_id"]
237 assert object.data["context_id"]
238 end
239
240 test "adds an id to a given object if it lacks one and is a note and inserts it to the object database" do
241 user = insert(:user)
242
243 data = %{
244 "actor" => user.ap_id,
245 "to" => [],
246 "object" => %{
247 "actor" => user.ap_id,
248 "to" => [],
249 "type" => "Note",
250 "content" => "hey"
251 }
252 }
253
254 {:ok, %Activity{} = activity} = ActivityPub.insert(data)
255 assert object = Object.normalize(activity)
256 assert is_binary(object.data["id"])
257 end
258 end
259
260 describe "create activities" do
261 test "removes doubled 'to' recipients" do
262 user = insert(:user)
263
264 {:ok, activity} =
265 ActivityPub.create(%{
266 to: ["user1", "user1", "user2"],
267 actor: user,
268 context: "",
269 object: %{
270 "to" => ["user1", "user1", "user2"],
271 "type" => "Note",
272 "content" => "testing"
273 }
274 })
275
276 assert activity.data["to"] == ["user1", "user2"]
277 assert activity.actor == user.ap_id
278 assert activity.recipients == ["user1", "user2", user.ap_id]
279 end
280
281 test "increases user note count only for public activities" do
282 user = insert(:user)
283
284 {:ok, _} =
285 CommonAPI.post(User.get_cached_by_id(user.id), %{
286 "status" => "1",
287 "visibility" => "public"
288 })
289
290 {:ok, _} =
291 CommonAPI.post(User.get_cached_by_id(user.id), %{
292 "status" => "2",
293 "visibility" => "unlisted"
294 })
295
296 {:ok, _} =
297 CommonAPI.post(User.get_cached_by_id(user.id), %{
298 "status" => "2",
299 "visibility" => "private"
300 })
301
302 {:ok, _} =
303 CommonAPI.post(User.get_cached_by_id(user.id), %{
304 "status" => "3",
305 "visibility" => "direct"
306 })
307
308 user = User.get_cached_by_id(user.id)
309 assert user.info.note_count == 2
310 end
311
312 test "increases replies count" do
313 user = insert(:user)
314 user2 = insert(:user)
315
316 {:ok, activity} = CommonAPI.post(user, %{"status" => "1", "visibility" => "public"})
317 ap_id = activity.data["id"]
318 reply_data = %{"status" => "1", "in_reply_to_status_id" => activity.id}
319
320 # public
321 {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "public"))
322 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
323 assert object.data["repliesCount"] == 1
324
325 # unlisted
326 {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "unlisted"))
327 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
328 assert object.data["repliesCount"] == 2
329
330 # private
331 {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "private"))
332 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
333 assert object.data["repliesCount"] == 2
334
335 # direct
336 {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "direct"))
337 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
338 assert object.data["repliesCount"] == 2
339 end
340 end
341
342 describe "fetch activities for recipients" do
343 test "retrieve the activities for certain recipients" do
344 {:ok, activity_one} = ActivityBuilder.insert(%{"to" => ["someone"]})
345 {:ok, activity_two} = ActivityBuilder.insert(%{"to" => ["someone_else"]})
346 {:ok, _activity_three} = ActivityBuilder.insert(%{"to" => ["noone"]})
347
348 activities = ActivityPub.fetch_activities(["someone", "someone_else"])
349 assert length(activities) == 2
350 assert activities == [activity_one, activity_two]
351 end
352 end
353
354 describe "fetch activities in context" do
355 test "retrieves activities that have a given context" do
356 {:ok, activity} = ActivityBuilder.insert(%{"type" => "Create", "context" => "2hu"})
357 {:ok, activity_two} = ActivityBuilder.insert(%{"type" => "Create", "context" => "2hu"})
358 {:ok, _activity_three} = ActivityBuilder.insert(%{"type" => "Create", "context" => "3hu"})
359 {:ok, _activity_four} = ActivityBuilder.insert(%{"type" => "Announce", "context" => "2hu"})
360 activity_five = insert(:note_activity)
361 user = insert(:user)
362
363 {:ok, user} = User.block(user, %{ap_id: activity_five.data["actor"]})
364
365 activities = ActivityPub.fetch_activities_for_context("2hu", %{"blocking_user" => user})
366 assert activities == [activity_two, activity]
367 end
368 end
369
370 test "doesn't return blocked activities" do
371 activity_one = insert(:note_activity)
372 activity_two = insert(:note_activity)
373 activity_three = insert(:note_activity)
374 user = insert(:user)
375 booster = insert(:user)
376 {:ok, user} = User.block(user, %{ap_id: activity_one.data["actor"]})
377
378 activities =
379 ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
380
381 assert Enum.member?(activities, activity_two)
382 assert Enum.member?(activities, activity_three)
383 refute Enum.member?(activities, activity_one)
384
385 {:ok, user} = User.unblock(user, %{ap_id: activity_one.data["actor"]})
386
387 activities =
388 ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
389
390 assert Enum.member?(activities, activity_two)
391 assert Enum.member?(activities, activity_three)
392 assert Enum.member?(activities, activity_one)
393
394 {:ok, user} = User.block(user, %{ap_id: activity_three.data["actor"]})
395 {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster)
396 %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
397 activity_three = Activity.get_by_id(activity_three.id)
398
399 activities =
400 ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
401
402 assert Enum.member?(activities, activity_two)
403 refute Enum.member?(activities, activity_three)
404 refute Enum.member?(activities, boost_activity)
405 assert Enum.member?(activities, activity_one)
406
407 activities =
408 ActivityPub.fetch_activities([], %{"blocking_user" => nil, "skip_preload" => true})
409
410 assert Enum.member?(activities, activity_two)
411 assert Enum.member?(activities, activity_three)
412 assert Enum.member?(activities, boost_activity)
413 assert Enum.member?(activities, activity_one)
414 end
415
416 test "doesn't return transitive interactions concerning blocked users" do
417 blocker = insert(:user)
418 blockee = insert(:user)
419 friend = insert(:user)
420
421 {:ok, blocker} = User.block(blocker, blockee)
422
423 {:ok, activity_one} = CommonAPI.post(friend, %{"status" => "hey!"})
424
425 {:ok, activity_two} = CommonAPI.post(friend, %{"status" => "hey! @#{blockee.nickname}"})
426
427 {:ok, activity_three} = CommonAPI.post(blockee, %{"status" => "hey! @#{friend.nickname}"})
428
429 {:ok, activity_four} = CommonAPI.post(blockee, %{"status" => "hey! @#{blocker.nickname}"})
430
431 activities = ActivityPub.fetch_activities([], %{"blocking_user" => blocker})
432
433 assert Enum.member?(activities, activity_one)
434 refute Enum.member?(activities, activity_two)
435 refute Enum.member?(activities, activity_three)
436 refute Enum.member?(activities, activity_four)
437 end
438
439 test "doesn't return announce activities concerning blocked users" do
440 blocker = insert(:user)
441 blockee = insert(:user)
442 friend = insert(:user)
443
444 {:ok, blocker} = User.block(blocker, blockee)
445
446 {:ok, activity_one} = CommonAPI.post(friend, %{"status" => "hey!"})
447
448 {:ok, activity_two} = CommonAPI.post(blockee, %{"status" => "hey! @#{friend.nickname}"})
449
450 {:ok, activity_three, _} = CommonAPI.repeat(activity_two.id, friend)
451
452 activities =
453 ActivityPub.fetch_activities([], %{"blocking_user" => blocker})
454 |> Enum.map(fn act -> act.id end)
455
456 assert Enum.member?(activities, activity_one.id)
457 refute Enum.member?(activities, activity_two.id)
458 refute Enum.member?(activities, activity_three.id)
459 end
460
461 test "doesn't return activities from blocked domains" do
462 domain = "dogwhistle.zone"
463 domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"})
464 note = insert(:note, %{data: %{"actor" => domain_user.ap_id}})
465 activity = insert(:note_activity, %{note: note})
466 user = insert(:user)
467 {:ok, user} = User.block_domain(user, domain)
468
469 activities =
470 ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
471
472 refute activity in activities
473
474 followed_user = insert(:user)
475 ActivityPub.follow(user, followed_user)
476 {:ok, repeat_activity, _} = CommonAPI.repeat(activity.id, followed_user)
477
478 activities =
479 ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
480
481 refute repeat_activity in activities
482 end
483
484 test "doesn't return muted activities" do
485 activity_one = insert(:note_activity)
486 activity_two = insert(:note_activity)
487 activity_three = insert(:note_activity)
488 user = insert(:user)
489 booster = insert(:user)
490 {:ok, user} = User.mute(user, %User{ap_id: activity_one.data["actor"]})
491
492 activities =
493 ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true})
494
495 assert Enum.member?(activities, activity_two)
496 assert Enum.member?(activities, activity_three)
497 refute Enum.member?(activities, activity_one)
498
499 # Calling with 'with_muted' will deliver muted activities, too.
500 activities =
501 ActivityPub.fetch_activities([], %{
502 "muting_user" => user,
503 "with_muted" => true,
504 "skip_preload" => true
505 })
506
507 assert Enum.member?(activities, activity_two)
508 assert Enum.member?(activities, activity_three)
509 assert Enum.member?(activities, activity_one)
510
511 {:ok, user} = User.unmute(user, %User{ap_id: activity_one.data["actor"]})
512
513 activities =
514 ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true})
515
516 assert Enum.member?(activities, activity_two)
517 assert Enum.member?(activities, activity_three)
518 assert Enum.member?(activities, activity_one)
519
520 {:ok, user} = User.mute(user, %User{ap_id: activity_three.data["actor"]})
521 {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster)
522 %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
523 activity_three = Activity.get_by_id(activity_three.id)
524
525 activities =
526 ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true})
527
528 assert Enum.member?(activities, activity_two)
529 refute Enum.member?(activities, activity_three)
530 refute Enum.member?(activities, boost_activity)
531 assert Enum.member?(activities, activity_one)
532
533 activities = ActivityPub.fetch_activities([], %{"muting_user" => nil, "skip_preload" => true})
534
535 assert Enum.member?(activities, activity_two)
536 assert Enum.member?(activities, activity_three)
537 assert Enum.member?(activities, boost_activity)
538 assert Enum.member?(activities, activity_one)
539 end
540
541 test "does include announces on request" do
542 activity_three = insert(:note_activity)
543 user = insert(:user)
544 booster = insert(:user)
545
546 {:ok, user} = User.follow(user, booster)
547
548 {:ok, announce, _object} = CommonAPI.repeat(activity_three.id, booster)
549
550 [announce_activity] = ActivityPub.fetch_activities([user.ap_id | user.following])
551
552 assert announce_activity.id == announce.id
553 end
554
555 test "excludes reblogs on request" do
556 user = insert(:user)
557 {:ok, expected_activity} = ActivityBuilder.insert(%{"type" => "Create"}, %{:user => user})
558 {:ok, _} = ActivityBuilder.insert(%{"type" => "Announce"}, %{:user => user})
559
560 [activity] = ActivityPub.fetch_user_activities(user, nil, %{"exclude_reblogs" => "true"})
561
562 assert activity == expected_activity
563 end
564
565 describe "public fetch activities" do
566 test "doesn't retrieve unlisted activities" do
567 user = insert(:user)
568
569 {:ok, _unlisted_activity} =
570 CommonAPI.post(user, %{"status" => "yeah", "visibility" => "unlisted"})
571
572 {:ok, listed_activity} = CommonAPI.post(user, %{"status" => "yeah"})
573
574 [activity] = ActivityPub.fetch_public_activities()
575
576 assert activity == listed_activity
577 end
578
579 test "retrieves public activities" do
580 _activities = ActivityPub.fetch_public_activities()
581
582 %{public: public} = ActivityBuilder.public_and_non_public()
583
584 activities = ActivityPub.fetch_public_activities()
585 assert length(activities) == 1
586 assert Enum.at(activities, 0) == public
587 end
588
589 test "retrieves a maximum of 20 activities" do
590 activities = ActivityBuilder.insert_list(30)
591 last_expected = List.last(activities)
592
593 activities = ActivityPub.fetch_public_activities()
594 last = List.last(activities)
595
596 assert length(activities) == 20
597 assert last == last_expected
598 end
599
600 test "retrieves ids starting from a since_id" do
601 activities = ActivityBuilder.insert_list(30)
602 later_activities = ActivityBuilder.insert_list(10)
603 since_id = List.last(activities).id
604 last_expected = List.last(later_activities)
605
606 activities = ActivityPub.fetch_public_activities(%{"since_id" => since_id})
607 last = List.last(activities)
608
609 assert length(activities) == 10
610 assert last == last_expected
611 end
612
613 test "retrieves ids up to max_id" do
614 _first_activities = ActivityBuilder.insert_list(10)
615 activities = ActivityBuilder.insert_list(20)
616 later_activities = ActivityBuilder.insert_list(10)
617 max_id = List.first(later_activities).id
618 last_expected = List.last(activities)
619
620 activities = ActivityPub.fetch_public_activities(%{"max_id" => max_id})
621 last = List.last(activities)
622
623 assert length(activities) == 20
624 assert last == last_expected
625 end
626
627 test "doesn't return reblogs for users for whom reblogs have been muted" do
628 activity = insert(:note_activity)
629 user = insert(:user)
630 booster = insert(:user)
631 {:ok, user} = CommonAPI.hide_reblogs(user, booster)
632
633 {:ok, activity, _} = CommonAPI.repeat(activity.id, booster)
634
635 activities = ActivityPub.fetch_activities([], %{"muting_user" => user})
636
637 refute Enum.any?(activities, fn %{id: id} -> id == activity.id end)
638 end
639
640 test "returns reblogs for users for whom reblogs have not been muted" do
641 activity = insert(:note_activity)
642 user = insert(:user)
643 booster = insert(:user)
644 {:ok, user} = CommonAPI.hide_reblogs(user, booster)
645 {:ok, user} = CommonAPI.show_reblogs(user, booster)
646
647 {:ok, activity, _} = CommonAPI.repeat(activity.id, booster)
648
649 activities = ActivityPub.fetch_activities([], %{"muting_user" => user})
650
651 assert Enum.any?(activities, fn %{id: id} -> id == activity.id end)
652 end
653 end
654
655 describe "like an object" do
656 test "adds a like activity to the db" do
657 note_activity = insert(:note_activity)
658 assert object = Object.normalize(note_activity)
659
660 user = insert(:user)
661 user_two = insert(:user)
662
663 {:ok, like_activity, object} = ActivityPub.like(user, object)
664
665 assert like_activity.data["actor"] == user.ap_id
666 assert like_activity.data["type"] == "Like"
667 assert like_activity.data["object"] == object.data["id"]
668 assert like_activity.data["to"] == [User.ap_followers(user), note_activity.data["actor"]]
669 assert like_activity.data["context"] == object.data["context"]
670 assert object.data["like_count"] == 1
671 assert object.data["likes"] == [user.ap_id]
672
673 # Just return the original activity if the user already liked it.
674 {:ok, same_like_activity, object} = ActivityPub.like(user, object)
675
676 assert like_activity == same_like_activity
677 assert object.data["likes"] == [user.ap_id]
678 assert object.data["like_count"] == 1
679
680 [note_activity] = Activity.get_all_create_by_object_ap_id(object.data["id"])
681 assert note_activity.data["object"]["like_count"] == 1
682
683 {:ok, _like_activity, object} = ActivityPub.like(user_two, object)
684 assert object.data["like_count"] == 2
685
686 [note_activity] = Activity.get_all_create_by_object_ap_id(object.data["id"])
687 assert note_activity.data["object"]["like_count"] == 2
688 end
689 end
690
691 describe "unliking" do
692 test "unliking a previously liked object" do
693 note_activity = insert(:note_activity)
694 object = Object.normalize(note_activity)
695 user = insert(:user)
696
697 # Unliking something that hasn't been liked does nothing
698 {:ok, object} = ActivityPub.unlike(user, object)
699 assert object.data["like_count"] == 0
700
701 {:ok, like_activity, object} = ActivityPub.like(user, object)
702 assert object.data["like_count"] == 1
703
704 {:ok, _, _, object} = ActivityPub.unlike(user, object)
705 assert object.data["like_count"] == 0
706
707 assert Activity.get_by_id(like_activity.id) == nil
708 end
709 end
710
711 describe "announcing an object" do
712 test "adds an announce activity to the db" do
713 note_activity = insert(:note_activity)
714 object = Object.normalize(note_activity)
715 user = insert(:user)
716
717 {:ok, announce_activity, object} = ActivityPub.announce(user, object)
718 assert object.data["announcement_count"] == 1
719 assert object.data["announcements"] == [user.ap_id]
720
721 assert announce_activity.data["to"] == [
722 User.ap_followers(user),
723 note_activity.data["actor"]
724 ]
725
726 assert announce_activity.data["object"] == object.data["id"]
727 assert announce_activity.data["actor"] == user.ap_id
728 assert announce_activity.data["context"] == object.data["context"]
729 end
730 end
731
732 describe "unannouncing an object" do
733 test "unannouncing a previously announced object" do
734 note_activity = insert(:note_activity)
735 object = Object.normalize(note_activity)
736 user = insert(:user)
737
738 # Unannouncing an object that is not announced does nothing
739 # {:ok, object} = ActivityPub.unannounce(user, object)
740 # assert object.data["announcement_count"] == 0
741
742 {:ok, announce_activity, object} = ActivityPub.announce(user, object)
743 assert object.data["announcement_count"] == 1
744
745 {:ok, unannounce_activity, object} = ActivityPub.unannounce(user, object)
746 assert object.data["announcement_count"] == 0
747
748 assert unannounce_activity.data["to"] == [
749 User.ap_followers(user),
750 announce_activity.data["actor"]
751 ]
752
753 assert unannounce_activity.data["type"] == "Undo"
754 assert unannounce_activity.data["object"] == announce_activity.data
755 assert unannounce_activity.data["actor"] == user.ap_id
756 assert unannounce_activity.data["context"] == announce_activity.data["context"]
757
758 assert Activity.get_by_id(announce_activity.id) == nil
759 end
760 end
761
762 describe "uploading files" do
763 test "copies the file to the configured folder" do
764 file = %Plug.Upload{
765 content_type: "image/jpg",
766 path: Path.absname("test/fixtures/image.jpg"),
767 filename: "an_image.jpg"
768 }
769
770 {:ok, %Object{} = object} = ActivityPub.upload(file)
771 assert object.data["name"] == "an_image.jpg"
772 end
773
774 test "works with base64 encoded images" do
775 file = %{
776 "img" => data_uri()
777 }
778
779 {:ok, %Object{}} = ActivityPub.upload(file)
780 end
781 end
782
783 describe "fetch the latest Follow" do
784 test "fetches the latest Follow activity" do
785 %Activity{data: %{"type" => "Follow"}} = activity = insert(:follow_activity)
786 follower = Repo.get_by(User, ap_id: activity.data["actor"])
787 followed = Repo.get_by(User, ap_id: activity.data["object"])
788
789 assert activity == Utils.fetch_latest_follow(follower, followed)
790 end
791 end
792
793 describe "following / unfollowing" do
794 test "creates a follow activity" do
795 follower = insert(:user)
796 followed = insert(:user)
797
798 {:ok, activity} = ActivityPub.follow(follower, followed)
799 assert activity.data["type"] == "Follow"
800 assert activity.data["actor"] == follower.ap_id
801 assert activity.data["object"] == followed.ap_id
802 end
803
804 test "creates an undo activity for the last follow" do
805 follower = insert(:user)
806 followed = insert(:user)
807
808 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
809 {:ok, activity} = ActivityPub.unfollow(follower, followed)
810
811 assert activity.data["type"] == "Undo"
812 assert activity.data["actor"] == follower.ap_id
813
814 embedded_object = activity.data["object"]
815 assert is_map(embedded_object)
816 assert embedded_object["type"] == "Follow"
817 assert embedded_object["object"] == followed.ap_id
818 assert embedded_object["id"] == follow_activity.data["id"]
819 end
820 end
821
822 describe "blocking / unblocking" do
823 test "creates a block activity" do
824 blocker = insert(:user)
825 blocked = insert(:user)
826
827 {:ok, activity} = ActivityPub.block(blocker, blocked)
828
829 assert activity.data["type"] == "Block"
830 assert activity.data["actor"] == blocker.ap_id
831 assert activity.data["object"] == blocked.ap_id
832 end
833
834 test "creates an undo activity for the last block" do
835 blocker = insert(:user)
836 blocked = insert(:user)
837
838 {:ok, block_activity} = ActivityPub.block(blocker, blocked)
839 {:ok, activity} = ActivityPub.unblock(blocker, blocked)
840
841 assert activity.data["type"] == "Undo"
842 assert activity.data["actor"] == blocker.ap_id
843
844 embedded_object = activity.data["object"]
845 assert is_map(embedded_object)
846 assert embedded_object["type"] == "Block"
847 assert embedded_object["object"] == blocked.ap_id
848 assert embedded_object["id"] == block_activity.data["id"]
849 end
850 end
851
852 describe "deletion" do
853 test "it creates a delete activity and deletes the original object" do
854 note = insert(:note_activity)
855 object = Object.normalize(note)
856 {:ok, delete} = ActivityPub.delete(object)
857
858 assert delete.data["type"] == "Delete"
859 assert delete.data["actor"] == note.data["actor"]
860 assert delete.data["object"] == object.data["id"]
861
862 assert Activity.get_by_id(delete.id) != nil
863
864 assert Repo.get(Object, object.id).data["type"] == "Tombstone"
865 end
866
867 test "decrements user note count only for public activities" do
868 user = insert(:user, info: %{note_count: 10})
869
870 {:ok, a1} =
871 CommonAPI.post(User.get_cached_by_id(user.id), %{
872 "status" => "yeah",
873 "visibility" => "public"
874 })
875
876 {:ok, a2} =
877 CommonAPI.post(User.get_cached_by_id(user.id), %{
878 "status" => "yeah",
879 "visibility" => "unlisted"
880 })
881
882 {:ok, a3} =
883 CommonAPI.post(User.get_cached_by_id(user.id), %{
884 "status" => "yeah",
885 "visibility" => "private"
886 })
887
888 {:ok, a4} =
889 CommonAPI.post(User.get_cached_by_id(user.id), %{
890 "status" => "yeah",
891 "visibility" => "direct"
892 })
893
894 {:ok, _} = Object.normalize(a1) |> ActivityPub.delete()
895 {:ok, _} = Object.normalize(a2) |> ActivityPub.delete()
896 {:ok, _} = Object.normalize(a3) |> ActivityPub.delete()
897 {:ok, _} = Object.normalize(a4) |> ActivityPub.delete()
898
899 user = User.get_cached_by_id(user.id)
900 assert user.info.note_count == 10
901 end
902
903 test "it creates a delete activity and checks that it is also sent to users mentioned by the deleted object" do
904 user = insert(:user)
905 note = insert(:note_activity)
906 object = Object.normalize(note)
907
908 {:ok, object} =
909 object
910 |> Object.change(%{
911 data: %{
912 "actor" => object.data["actor"],
913 "id" => object.data["id"],
914 "to" => [user.ap_id],
915 "type" => "Note"
916 }
917 })
918 |> Object.update_and_set_cache()
919
920 {:ok, delete} = ActivityPub.delete(object)
921
922 assert user.ap_id in delete.data["to"]
923 end
924
925 test "decreases reply count" do
926 user = insert(:user)
927 user2 = insert(:user)
928
929 {:ok, activity} = CommonAPI.post(user, %{"status" => "1", "visibility" => "public"})
930 reply_data = %{"status" => "1", "in_reply_to_status_id" => activity.id}
931 ap_id = activity.data["id"]
932
933 {:ok, public_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "public"))
934 {:ok, unlisted_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "unlisted"))
935 {:ok, private_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "private"))
936 {:ok, direct_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "direct"))
937
938 _ = CommonAPI.delete(direct_reply.id, user2)
939 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
940 assert object.data["repliesCount"] == 2
941
942 _ = CommonAPI.delete(private_reply.id, user2)
943 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
944 assert object.data["repliesCount"] == 2
945
946 _ = CommonAPI.delete(public_reply.id, user2)
947 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
948 assert object.data["repliesCount"] == 1
949
950 _ = CommonAPI.delete(unlisted_reply.id, user2)
951 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
952 assert object.data["repliesCount"] == 0
953 end
954 end
955
956 describe "timeline post-processing" do
957 test "it filters broken threads" do
958 user1 = insert(:user)
959 user2 = insert(:user)
960 user3 = insert(:user)
961
962 {:ok, user1} = User.follow(user1, user3)
963 assert User.following?(user1, user3)
964
965 {:ok, user2} = User.follow(user2, user3)
966 assert User.following?(user2, user3)
967
968 {:ok, user3} = User.follow(user3, user2)
969 assert User.following?(user3, user2)
970
971 {:ok, public_activity} = CommonAPI.post(user3, %{"status" => "hi 1"})
972
973 {:ok, private_activity_1} =
974 CommonAPI.post(user3, %{"status" => "hi 2", "visibility" => "private"})
975
976 {:ok, private_activity_2} =
977 CommonAPI.post(user2, %{
978 "status" => "hi 3",
979 "visibility" => "private",
980 "in_reply_to_status_id" => private_activity_1.id
981 })
982
983 {:ok, private_activity_3} =
984 CommonAPI.post(user3, %{
985 "status" => "hi 4",
986 "visibility" => "private",
987 "in_reply_to_status_id" => private_activity_2.id
988 })
989
990 activities =
991 ActivityPub.fetch_activities([user1.ap_id | user1.following])
992 |> Enum.map(fn a -> a.id end)
993
994 private_activity_1 = Activity.get_by_ap_id_with_object(private_activity_1.data["id"])
995
996 assert [public_activity.id, private_activity_1.id, private_activity_3.id] == activities
997
998 assert length(activities) == 3
999
1000 activities =
1001 ActivityPub.fetch_activities([user1.ap_id | user1.following], %{"user" => user1})
1002 |> Enum.map(fn a -> a.id end)
1003
1004 assert [public_activity.id, private_activity_1.id] == activities
1005 assert length(activities) == 2
1006 end
1007 end
1008
1009 describe "update" do
1010 test "it creates an update activity with the new user data" do
1011 user = insert(:user)
1012 {:ok, user} = User.ensure_keys_present(user)
1013 user_data = Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user})
1014
1015 {:ok, update} =
1016 ActivityPub.update(%{
1017 actor: user_data["id"],
1018 to: [user.follower_address],
1019 cc: [],
1020 object: user_data
1021 })
1022
1023 assert update.data["actor"] == user.ap_id
1024 assert update.data["to"] == [user.follower_address]
1025 assert embedded_object = update.data["object"]
1026 assert embedded_object["id"] == user_data["id"]
1027 assert embedded_object["type"] == user_data["type"]
1028 end
1029 end
1030
1031 test "returned pinned statuses" do
1032 Pleroma.Config.put([:instance, :max_pinned_statuses], 3)
1033 user = insert(:user)
1034
1035 {:ok, activity_one} = CommonAPI.post(user, %{"status" => "HI!!!"})
1036 {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
1037 {:ok, activity_three} = CommonAPI.post(user, %{"status" => "HI!!!"})
1038
1039 CommonAPI.pin(activity_one.id, user)
1040 user = refresh_record(user)
1041
1042 CommonAPI.pin(activity_two.id, user)
1043 user = refresh_record(user)
1044
1045 CommonAPI.pin(activity_three.id, user)
1046 user = refresh_record(user)
1047
1048 activities = ActivityPub.fetch_user_activities(user, nil, %{"pinned" => "true"})
1049
1050 assert 3 = length(activities)
1051 end
1052
1053 test "it can create a Flag activity" do
1054 reporter = insert(:user)
1055 target_account = insert(:user)
1056 {:ok, activity} = CommonAPI.post(target_account, %{"status" => "foobar"})
1057 context = Utils.generate_context_id()
1058 content = "foobar"
1059
1060 reporter_ap_id = reporter.ap_id
1061 target_ap_id = target_account.ap_id
1062 activity_ap_id = activity.data["id"]
1063
1064 assert {:ok, activity} =
1065 ActivityPub.flag(%{
1066 actor: reporter,
1067 context: context,
1068 account: target_account,
1069 statuses: [activity],
1070 content: content
1071 })
1072
1073 assert %Activity{
1074 actor: ^reporter_ap_id,
1075 data: %{
1076 "type" => "Flag",
1077 "content" => ^content,
1078 "context" => ^context,
1079 "object" => [^target_ap_id, ^activity_ap_id]
1080 }
1081 } = activity
1082 end
1083
1084 test "fetch_activities/2 returns activities addressed to a list " do
1085 user = insert(:user)
1086 member = insert(:user)
1087 {:ok, list} = Pleroma.List.create("foo", user)
1088 {:ok, list} = Pleroma.List.follow(list, member)
1089
1090 {:ok, activity} =
1091 CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"})
1092
1093 activity = Repo.preload(activity, :bookmark)
1094 activity = %Activity{activity | thread_muted?: !!activity.thread_muted?}
1095
1096 assert ActivityPub.fetch_activities([], %{"user" => user}) == [activity]
1097 end
1098
1099 def data_uri do
1100 File.read!("test/fixtures/avatar_data_uri")
1101 end
1102
1103 describe "fetch_activities_bounded" do
1104 test "fetches private posts for followed users" do
1105 user = insert(:user)
1106
1107 {:ok, activity} =
1108 CommonAPI.post(user, %{
1109 "status" => "thought I looked cute might delete later :3",
1110 "visibility" => "private"
1111 })
1112
1113 [result] = ActivityPub.fetch_activities_bounded([user.follower_address], [])
1114 assert result.id == activity.id
1115 end
1116
1117 test "fetches only public posts for other users" do
1118 user = insert(:user)
1119 {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe", "visibility" => "public"})
1120
1121 {:ok, _private_activity} =
1122 CommonAPI.post(user, %{
1123 "status" => "why is tenshi eating a corndog so cute?",
1124 "visibility" => "private"
1125 })
1126
1127 [result] = ActivityPub.fetch_activities_bounded([], [user.follower_address])
1128 assert result.id == activity.id
1129 end
1130 end
1131
1132 describe "fetch_follow_information_for_user" do
1133 test "syncronizes following/followers counters" do
1134 user =
1135 insert(:user,
1136 local: false,
1137 follower_address: "http://localhost:4001/users/fuser2/followers",
1138 following_address: "http://localhost:4001/users/fuser2/following"
1139 )
1140
1141 {:ok, info} = ActivityPub.fetch_follow_information_for_user(user)
1142 assert info.follower_count == 527
1143 assert info.following_count == 267
1144 end
1145
1146 test "detects hidden followers" do
1147 mock(fn env ->
1148 case env.url do
1149 "http://localhost:4001/users/masto_closed/followers?page=1" ->
1150 %Tesla.Env{status: 403, body: ""}
1151
1152 "http://localhost:4001/users/masto_closed/following?page=1" ->
1153 %Tesla.Env{
1154 status: 200,
1155 body:
1156 Jason.encode!(%{
1157 "id" => "http://localhost:4001/users/masto_closed/following?page=1",
1158 "type" => "OrderedCollectionPage"
1159 })
1160 }
1161
1162 _ ->
1163 apply(HttpRequestMock, :request, [env])
1164 end
1165 end)
1166
1167 user =
1168 insert(:user,
1169 local: false,
1170 follower_address: "http://localhost:4001/users/masto_closed/followers",
1171 following_address: "http://localhost:4001/users/masto_closed/following"
1172 )
1173
1174 {:ok, info} = ActivityPub.fetch_follow_information_for_user(user)
1175 assert info.hide_followers == true
1176 assert info.hide_follows == false
1177 end
1178
1179 test "detects hidden follows" do
1180 mock(fn env ->
1181 case env.url do
1182 "http://localhost:4001/users/masto_closed/following?page=1" ->
1183 %Tesla.Env{status: 403, body: ""}
1184
1185 "http://localhost:4001/users/masto_closed/followers?page=1" ->
1186 %Tesla.Env{
1187 status: 200,
1188 body:
1189 Jason.encode!(%{
1190 "id" => "http://localhost:4001/users/masto_closed/followers?page=1",
1191 "type" => "OrderedCollectionPage"
1192 })
1193 }
1194
1195 _ ->
1196 apply(HttpRequestMock, :request, [env])
1197 end
1198 end)
1199
1200 user =
1201 insert(:user,
1202 local: false,
1203 follower_address: "http://localhost:4001/users/masto_closed/followers",
1204 following_address: "http://localhost:4001/users/masto_closed/following"
1205 )
1206
1207 {:ok, info} = ActivityPub.fetch_follow_information_for_user(user)
1208 assert info.hide_followers == false
1209 assert info.hide_follows == true
1210 end
1211 end
1212 end