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