Merge remote-tracking branch 'pleroma/develop' into feature/addressable-lists
[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 = ActivityPub.fetch_activities([user1.ap_id | user1.following])
964
965 private_activity_1 = Activity.get_by_ap_id_with_object(private_activity_1.data["id"])
966
967 assert [public_activity, private_activity_1, private_activity_3] == activities
968
969 assert length(activities) == 3
970
971 activities = ActivityPub.contain_timeline(activities, user1)
972
973 assert [public_activity, private_activity_1] == activities
974 assert length(activities) == 2
975 end
976 end
977
978 describe "update" do
979 test "it creates an update activity with the new user data" do
980 user = insert(:user)
981 {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user)
982 user_data = Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user})
983
984 {:ok, update} =
985 ActivityPub.update(%{
986 actor: user_data["id"],
987 to: [user.follower_address],
988 cc: [],
989 object: user_data
990 })
991
992 assert update.data["actor"] == user.ap_id
993 assert update.data["to"] == [user.follower_address]
994 assert update.data["object"]["id"] == user_data["id"]
995 assert update.data["object"]["type"] == user_data["type"]
996 end
997 end
998
999 test "returned pinned statuses" do
1000 Pleroma.Config.put([:instance, :max_pinned_statuses], 3)
1001 user = insert(:user)
1002
1003 {:ok, activity_one} = CommonAPI.post(user, %{"status" => "HI!!!"})
1004 {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
1005 {:ok, activity_three} = CommonAPI.post(user, %{"status" => "HI!!!"})
1006
1007 CommonAPI.pin(activity_one.id, user)
1008 user = refresh_record(user)
1009
1010 CommonAPI.pin(activity_two.id, user)
1011 user = refresh_record(user)
1012
1013 CommonAPI.pin(activity_three.id, user)
1014 user = refresh_record(user)
1015
1016 activities = ActivityPub.fetch_user_activities(user, nil, %{"pinned" => "true"})
1017
1018 assert 3 = length(activities)
1019 end
1020
1021 test "it can create a Flag activity" do
1022 reporter = insert(:user)
1023 target_account = insert(:user)
1024 {:ok, activity} = CommonAPI.post(target_account, %{"status" => "foobar"})
1025 context = Utils.generate_context_id()
1026 content = "foobar"
1027
1028 reporter_ap_id = reporter.ap_id
1029 target_ap_id = target_account.ap_id
1030 activity_ap_id = activity.data["id"]
1031
1032 assert {:ok, activity} =
1033 ActivityPub.flag(%{
1034 actor: reporter,
1035 context: context,
1036 account: target_account,
1037 statuses: [activity],
1038 content: content
1039 })
1040
1041 assert %Activity{
1042 actor: ^reporter_ap_id,
1043 data: %{
1044 "type" => "Flag",
1045 "content" => ^content,
1046 "context" => ^context,
1047 "object" => [^target_ap_id, ^activity_ap_id]
1048 }
1049 } = activity
1050 end
1051
1052 describe "publish_one/1" do
1053 test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is not specified",
1054 Instances,
1055 [:passthrough],
1056 [] do
1057 actor = insert(:user)
1058 inbox = "http://200.site/users/nick1/inbox"
1059
1060 assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
1061
1062 assert called(Instances.set_reachable(inbox))
1063 end
1064
1065 test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is set",
1066 Instances,
1067 [:passthrough],
1068 [] do
1069 actor = insert(:user)
1070 inbox = "http://200.site/users/nick1/inbox"
1071
1072 assert {:ok, _} =
1073 Publisher.publish_one(%{
1074 inbox: inbox,
1075 json: "{}",
1076 actor: actor,
1077 id: 1,
1078 unreachable_since: NaiveDateTime.utc_now()
1079 })
1080
1081 assert called(Instances.set_reachable(inbox))
1082 end
1083
1084 test_with_mock "does NOT call `Instances.set_reachable` on successful federation if `unreachable_since` is nil",
1085 Instances,
1086 [:passthrough],
1087 [] do
1088 actor = insert(:user)
1089 inbox = "http://200.site/users/nick1/inbox"
1090
1091 assert {:ok, _} =
1092 Publisher.publish_one(%{
1093 inbox: inbox,
1094 json: "{}",
1095 actor: actor,
1096 id: 1,
1097 unreachable_since: nil
1098 })
1099
1100 refute called(Instances.set_reachable(inbox))
1101 end
1102
1103 test_with_mock "calls `Instances.set_unreachable` on target inbox on non-2xx HTTP response code",
1104 Instances,
1105 [:passthrough],
1106 [] do
1107 actor = insert(:user)
1108 inbox = "http://404.site/users/nick1/inbox"
1109
1110 assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
1111
1112 assert called(Instances.set_unreachable(inbox))
1113 end
1114
1115 test_with_mock "it calls `Instances.set_unreachable` on target inbox on request error of any kind",
1116 Instances,
1117 [:passthrough],
1118 [] do
1119 actor = insert(:user)
1120 inbox = "http://connrefused.site/users/nick1/inbox"
1121
1122 assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
1123
1124 assert called(Instances.set_unreachable(inbox))
1125 end
1126
1127 test_with_mock "does NOT call `Instances.set_unreachable` if target is reachable",
1128 Instances,
1129 [:passthrough],
1130 [] do
1131 actor = insert(:user)
1132 inbox = "http://200.site/users/nick1/inbox"
1133
1134 assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
1135
1136 refute called(Instances.set_unreachable(inbox))
1137 end
1138
1139 test_with_mock "does NOT call `Instances.set_unreachable` if target instance has non-nil `unreachable_since`",
1140 Instances,
1141 [:passthrough],
1142 [] do
1143 actor = insert(:user)
1144 inbox = "http://connrefused.site/users/nick1/inbox"
1145
1146 assert {:error, _} =
1147 Publisher.publish_one(%{
1148 inbox: inbox,
1149 json: "{}",
1150 actor: actor,
1151 id: 1,
1152 unreachable_since: NaiveDateTime.utc_now()
1153 })
1154
1155 refute called(Instances.set_unreachable(inbox))
1156 end
1157 end
1158
1159 def data_uri do
1160 File.read!("test/fixtures/avatar_data_uri")
1161 end
1162 end