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