Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into feature/digest...
[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 announce activities concerning blocked users" do
368 blocker = insert(:user)
369 blockee = insert(:user)
370 friend = insert(:user)
371
372 {:ok, blocker} = User.block(blocker, blockee)
373
374 {:ok, activity_one} = CommonAPI.post(friend, %{"status" => "hey!"})
375
376 {:ok, activity_two} = CommonAPI.post(blockee, %{"status" => "hey! @#{friend.nickname}"})
377
378 {:ok, activity_three, _} = CommonAPI.repeat(activity_two.id, friend)
379
380 activities =
381 ActivityPub.fetch_activities([], %{"blocking_user" => blocker})
382 |> Enum.map(fn act -> act.id end)
383
384 assert Enum.member?(activities, activity_one.id)
385 refute Enum.member?(activities, activity_two.id)
386 refute Enum.member?(activities, activity_three.id)
387 end
388
389 test "doesn't return muted activities" do
390 activity_one = insert(:note_activity)
391 activity_two = insert(:note_activity)
392 activity_three = insert(:note_activity)
393 user = insert(:user)
394 booster = insert(:user)
395 {:ok, user} = User.mute(user, %User{ap_id: activity_one.data["actor"]})
396
397 activities =
398 ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true})
399
400 assert Enum.member?(activities, activity_two)
401 assert Enum.member?(activities, activity_three)
402 refute Enum.member?(activities, activity_one)
403
404 # Calling with 'with_muted' will deliver muted activities, too.
405 activities =
406 ActivityPub.fetch_activities([], %{
407 "muting_user" => user,
408 "with_muted" => true,
409 "skip_preload" => true
410 })
411
412 assert Enum.member?(activities, activity_two)
413 assert Enum.member?(activities, activity_three)
414 assert Enum.member?(activities, activity_one)
415
416 {:ok, user} = User.unmute(user, %User{ap_id: activity_one.data["actor"]})
417
418 activities =
419 ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true})
420
421 assert Enum.member?(activities, activity_two)
422 assert Enum.member?(activities, activity_three)
423 assert Enum.member?(activities, activity_one)
424
425 {:ok, user} = User.mute(user, %User{ap_id: activity_three.data["actor"]})
426 {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster)
427 %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
428 activity_three = Activity.get_by_id(activity_three.id)
429
430 activities =
431 ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true})
432
433 assert Enum.member?(activities, activity_two)
434 refute Enum.member?(activities, activity_three)
435 refute Enum.member?(activities, boost_activity)
436 assert Enum.member?(activities, activity_one)
437
438 activities = ActivityPub.fetch_activities([], %{"muting_user" => nil, "skip_preload" => true})
439
440 assert Enum.member?(activities, activity_two)
441 assert Enum.member?(activities, activity_three)
442 assert Enum.member?(activities, boost_activity)
443 assert Enum.member?(activities, activity_one)
444 end
445
446 test "does include announces on request" do
447 activity_three = insert(:note_activity)
448 user = insert(:user)
449 booster = insert(:user)
450
451 {:ok, user} = User.follow(user, booster)
452
453 {:ok, announce, _object} = CommonAPI.repeat(activity_three.id, booster)
454
455 [announce_activity] = ActivityPub.fetch_activities([user.ap_id | user.following])
456
457 assert announce_activity.id == announce.id
458 end
459
460 test "excludes reblogs on request" do
461 user = insert(:user)
462 {:ok, expected_activity} = ActivityBuilder.insert(%{"type" => "Create"}, %{:user => user})
463 {:ok, _} = ActivityBuilder.insert(%{"type" => "Announce"}, %{:user => user})
464
465 [activity] = ActivityPub.fetch_user_activities(user, nil, %{"exclude_reblogs" => "true"})
466
467 assert activity == expected_activity
468 end
469
470 describe "public fetch activities" do
471 test "doesn't retrieve unlisted activities" do
472 user = insert(:user)
473
474 {:ok, _unlisted_activity} =
475 CommonAPI.post(user, %{"status" => "yeah", "visibility" => "unlisted"})
476
477 {:ok, listed_activity} = CommonAPI.post(user, %{"status" => "yeah"})
478
479 [activity] = ActivityPub.fetch_public_activities()
480
481 assert activity == listed_activity
482 end
483
484 test "retrieves public activities" do
485 _activities = ActivityPub.fetch_public_activities()
486
487 %{public: public} = ActivityBuilder.public_and_non_public()
488
489 activities = ActivityPub.fetch_public_activities()
490 assert length(activities) == 1
491 assert Enum.at(activities, 0) == public
492 end
493
494 test "retrieves a maximum of 20 activities" do
495 activities = ActivityBuilder.insert_list(30)
496 last_expected = List.last(activities)
497
498 activities = ActivityPub.fetch_public_activities()
499 last = List.last(activities)
500
501 assert length(activities) == 20
502 assert last == last_expected
503 end
504
505 test "retrieves ids starting from a since_id" do
506 activities = ActivityBuilder.insert_list(30)
507 later_activities = ActivityBuilder.insert_list(10)
508 since_id = List.last(activities).id
509 last_expected = List.last(later_activities)
510
511 activities = ActivityPub.fetch_public_activities(%{"since_id" => since_id})
512 last = List.last(activities)
513
514 assert length(activities) == 10
515 assert last == last_expected
516 end
517
518 test "retrieves ids up to max_id" do
519 _first_activities = ActivityBuilder.insert_list(10)
520 activities = ActivityBuilder.insert_list(20)
521 later_activities = ActivityBuilder.insert_list(10)
522 max_id = List.first(later_activities).id
523 last_expected = List.last(activities)
524
525 activities = ActivityPub.fetch_public_activities(%{"max_id" => max_id})
526 last = List.last(activities)
527
528 assert length(activities) == 20
529 assert last == last_expected
530 end
531
532 test "doesn't return reblogs for users for whom reblogs have been muted" do
533 activity = insert(:note_activity)
534 user = insert(:user)
535 booster = insert(:user)
536 {:ok, user} = CommonAPI.hide_reblogs(user, booster)
537
538 {:ok, activity, _} = CommonAPI.repeat(activity.id, booster)
539
540 activities = ActivityPub.fetch_activities([], %{"muting_user" => user})
541
542 refute Enum.any?(activities, fn %{id: id} -> id == activity.id end)
543 end
544
545 test "returns reblogs for users for whom reblogs have not been muted" do
546 activity = insert(:note_activity)
547 user = insert(:user)
548 booster = insert(:user)
549 {:ok, user} = CommonAPI.hide_reblogs(user, booster)
550 {:ok, user} = CommonAPI.show_reblogs(user, booster)
551
552 {:ok, activity, _} = CommonAPI.repeat(activity.id, booster)
553
554 activities = ActivityPub.fetch_activities([], %{"muting_user" => user})
555
556 assert Enum.any?(activities, fn %{id: id} -> id == activity.id end)
557 end
558 end
559
560 describe "like an object" do
561 test "adds a like activity to the db" do
562 note_activity = insert(:note_activity)
563 object = Object.get_by_ap_id(note_activity.data["object"]["id"])
564 user = insert(:user)
565 user_two = insert(:user)
566
567 {:ok, like_activity, object} = ActivityPub.like(user, object)
568
569 assert like_activity.data["actor"] == user.ap_id
570 assert like_activity.data["type"] == "Like"
571 assert like_activity.data["object"] == object.data["id"]
572 assert like_activity.data["to"] == [User.ap_followers(user), note_activity.data["actor"]]
573 assert like_activity.data["context"] == object.data["context"]
574 assert object.data["like_count"] == 1
575 assert object.data["likes"] == [user.ap_id]
576
577 # Just return the original activity if the user already liked it.
578 {:ok, same_like_activity, object} = ActivityPub.like(user, object)
579
580 assert like_activity == same_like_activity
581 assert object.data["likes"] == [user.ap_id]
582
583 [note_activity] = Activity.get_all_create_by_object_ap_id(object.data["id"])
584 assert note_activity.data["object"]["like_count"] == 1
585
586 {:ok, _like_activity, object} = ActivityPub.like(user_two, object)
587 assert object.data["like_count"] == 2
588 end
589 end
590
591 describe "unliking" do
592 test "unliking a previously liked object" do
593 note_activity = insert(:note_activity)
594 object = Object.get_by_ap_id(note_activity.data["object"]["id"])
595 user = insert(:user)
596
597 # Unliking something that hasn't been liked does nothing
598 {:ok, object} = ActivityPub.unlike(user, object)
599 assert object.data["like_count"] == 0
600
601 {:ok, like_activity, object} = ActivityPub.like(user, object)
602 assert object.data["like_count"] == 1
603
604 {:ok, _, _, object} = ActivityPub.unlike(user, object)
605 assert object.data["like_count"] == 0
606
607 assert Activity.get_by_id(like_activity.id) == nil
608 end
609 end
610
611 describe "announcing an object" do
612 test "adds an announce activity to the db" do
613 note_activity = insert(:note_activity)
614 object = Object.get_by_ap_id(note_activity.data["object"]["id"])
615 user = insert(:user)
616
617 {:ok, announce_activity, object} = ActivityPub.announce(user, object)
618 assert object.data["announcement_count"] == 1
619 assert object.data["announcements"] == [user.ap_id]
620
621 assert announce_activity.data["to"] == [
622 User.ap_followers(user),
623 note_activity.data["actor"]
624 ]
625
626 assert announce_activity.data["object"] == object.data["id"]
627 assert announce_activity.data["actor"] == user.ap_id
628 assert announce_activity.data["context"] == object.data["context"]
629 end
630 end
631
632 describe "unannouncing an object" do
633 test "unannouncing a previously announced object" do
634 note_activity = insert(:note_activity)
635 object = Object.get_by_ap_id(note_activity.data["object"]["id"])
636 user = insert(:user)
637
638 # Unannouncing an object that is not announced does nothing
639 # {:ok, object} = ActivityPub.unannounce(user, object)
640 # assert object.data["announcement_count"] == 0
641
642 {:ok, announce_activity, object} = ActivityPub.announce(user, object)
643 assert object.data["announcement_count"] == 1
644
645 {:ok, unannounce_activity, object} = ActivityPub.unannounce(user, object)
646 assert object.data["announcement_count"] == 0
647
648 assert unannounce_activity.data["to"] == [
649 User.ap_followers(user),
650 announce_activity.data["actor"]
651 ]
652
653 assert unannounce_activity.data["type"] == "Undo"
654 assert unannounce_activity.data["object"] == announce_activity.data
655 assert unannounce_activity.data["actor"] == user.ap_id
656 assert unannounce_activity.data["context"] == announce_activity.data["context"]
657
658 assert Activity.get_by_id(announce_activity.id) == nil
659 end
660 end
661
662 describe "uploading files" do
663 test "copies the file to the configured folder" do
664 file = %Plug.Upload{
665 content_type: "image/jpg",
666 path: Path.absname("test/fixtures/image.jpg"),
667 filename: "an_image.jpg"
668 }
669
670 {:ok, %Object{} = object} = ActivityPub.upload(file)
671 assert object.data["name"] == "an_image.jpg"
672 end
673
674 test "works with base64 encoded images" do
675 file = %{
676 "img" => data_uri()
677 }
678
679 {:ok, %Object{}} = ActivityPub.upload(file)
680 end
681 end
682
683 describe "fetching an object" do
684 test "it fetches an object" do
685 {:ok, object} =
686 ActivityPub.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367")
687
688 assert activity = Activity.get_create_by_object_ap_id(object.data["id"])
689 assert activity.data["id"]
690
691 {:ok, object_again} =
692 ActivityPub.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367")
693
694 assert [attachment] = object.data["attachment"]
695 assert is_list(attachment["url"])
696
697 assert object == object_again
698 end
699
700 test "it works with objects only available via Ostatus" do
701 {:ok, object} = ActivityPub.fetch_object_from_id("https://shitposter.club/notice/2827873")
702 assert activity = Activity.get_create_by_object_ap_id(object.data["id"])
703 assert activity.data["id"]
704
705 {:ok, object_again} =
706 ActivityPub.fetch_object_from_id("https://shitposter.club/notice/2827873")
707
708 assert object == object_again
709 end
710
711 test "it correctly stitches up conversations between ostatus and ap" do
712 last = "https://mstdn.io/users/mayuutann/statuses/99568293732299394"
713 {:ok, object} = ActivityPub.fetch_object_from_id(last)
714
715 object = Object.get_by_ap_id(object.data["inReplyTo"])
716 assert object
717 end
718 end
719
720 describe "following / unfollowing" do
721 test "creates a follow activity" do
722 follower = insert(:user)
723 followed = insert(:user)
724
725 {:ok, activity} = ActivityPub.follow(follower, followed)
726 assert activity.data["type"] == "Follow"
727 assert activity.data["actor"] == follower.ap_id
728 assert activity.data["object"] == followed.ap_id
729 end
730
731 test "creates an undo activity for the last follow" do
732 follower = insert(:user)
733 followed = insert(:user)
734
735 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
736 {:ok, activity} = ActivityPub.unfollow(follower, followed)
737
738 assert activity.data["type"] == "Undo"
739 assert activity.data["actor"] == follower.ap_id
740
741 assert is_map(activity.data["object"])
742 assert activity.data["object"]["type"] == "Follow"
743 assert activity.data["object"]["object"] == followed.ap_id
744 assert activity.data["object"]["id"] == follow_activity.data["id"]
745 end
746 end
747
748 describe "blocking / unblocking" do
749 test "creates a block activity" do
750 blocker = insert(:user)
751 blocked = insert(:user)
752
753 {:ok, activity} = ActivityPub.block(blocker, blocked)
754
755 assert activity.data["type"] == "Block"
756 assert activity.data["actor"] == blocker.ap_id
757 assert activity.data["object"] == blocked.ap_id
758 end
759
760 test "creates an undo activity for the last block" do
761 blocker = insert(:user)
762 blocked = insert(:user)
763
764 {:ok, block_activity} = ActivityPub.block(blocker, blocked)
765 {:ok, activity} = ActivityPub.unblock(blocker, blocked)
766
767 assert activity.data["type"] == "Undo"
768 assert activity.data["actor"] == blocker.ap_id
769
770 assert is_map(activity.data["object"])
771 assert activity.data["object"]["type"] == "Block"
772 assert activity.data["object"]["object"] == blocked.ap_id
773 assert activity.data["object"]["id"] == block_activity.data["id"]
774 end
775 end
776
777 describe "deletion" do
778 test "it creates a delete activity and deletes the original object" do
779 note = insert(:note_activity)
780 object = Object.get_by_ap_id(note.data["object"]["id"])
781 {:ok, delete} = ActivityPub.delete(object)
782
783 assert delete.data["type"] == "Delete"
784 assert delete.data["actor"] == note.data["actor"]
785 assert delete.data["object"] == note.data["object"]["id"]
786
787 assert Activity.get_by_id(delete.id) != nil
788
789 assert Repo.get(Object, object.id).data["type"] == "Tombstone"
790 end
791
792 test "decrements user note count only for public activities" do
793 user = insert(:user, info: %{note_count: 10})
794
795 {:ok, a1} =
796 CommonAPI.post(User.get_by_id(user.id), %{"status" => "yeah", "visibility" => "public"})
797
798 {:ok, a2} =
799 CommonAPI.post(User.get_by_id(user.id), %{"status" => "yeah", "visibility" => "unlisted"})
800
801 {:ok, a3} =
802 CommonAPI.post(User.get_by_id(user.id), %{"status" => "yeah", "visibility" => "private"})
803
804 {:ok, a4} =
805 CommonAPI.post(User.get_by_id(user.id), %{"status" => "yeah", "visibility" => "direct"})
806
807 {:ok, _} = a1.data["object"]["id"] |> Object.get_by_ap_id() |> ActivityPub.delete()
808 {:ok, _} = a2.data["object"]["id"] |> Object.get_by_ap_id() |> ActivityPub.delete()
809 {:ok, _} = a3.data["object"]["id"] |> Object.get_by_ap_id() |> ActivityPub.delete()
810 {:ok, _} = a4.data["object"]["id"] |> Object.get_by_ap_id() |> ActivityPub.delete()
811
812 user = User.get_by_id(user.id)
813 assert user.info.note_count == 10
814 end
815
816 test "it creates a delete activity and checks that it is also sent to users mentioned by the deleted object" do
817 user = insert(:user)
818 note = insert(:note_activity)
819
820 {:ok, object} =
821 Object.get_by_ap_id(note.data["object"]["id"])
822 |> Object.change(%{
823 data: %{
824 "actor" => note.data["object"]["actor"],
825 "id" => note.data["object"]["id"],
826 "to" => [user.ap_id],
827 "type" => "Note"
828 }
829 })
830 |> Object.update_and_set_cache()
831
832 {:ok, delete} = ActivityPub.delete(object)
833
834 assert user.ap_id in delete.data["to"]
835 end
836
837 test "decreases reply count" do
838 user = insert(:user)
839 user2 = insert(:user)
840
841 {:ok, activity} = CommonAPI.post(user, %{"status" => "1", "visibility" => "public"})
842 reply_data = %{"status" => "1", "in_reply_to_status_id" => activity.id}
843 ap_id = activity.data["id"]
844
845 {:ok, public_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "public"))
846 {:ok, unlisted_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "unlisted"))
847 {:ok, private_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "private"))
848 {:ok, direct_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "direct"))
849
850 _ = CommonAPI.delete(direct_reply.id, user2)
851 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
852 assert data["object"]["repliesCount"] == 2
853 assert object.data["repliesCount"] == 2
854
855 _ = CommonAPI.delete(private_reply.id, user2)
856 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
857 assert data["object"]["repliesCount"] == 2
858 assert object.data["repliesCount"] == 2
859
860 _ = CommonAPI.delete(public_reply.id, user2)
861 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
862 assert data["object"]["repliesCount"] == 1
863 assert object.data["repliesCount"] == 1
864
865 _ = CommonAPI.delete(unlisted_reply.id, user2)
866 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
867 assert data["object"]["repliesCount"] == 0
868 assert object.data["repliesCount"] == 0
869 end
870 end
871
872 describe "timeline post-processing" do
873 test "it filters broken threads" do
874 user1 = insert(:user)
875 user2 = insert(:user)
876 user3 = insert(:user)
877
878 {:ok, user1} = User.follow(user1, user3)
879 assert User.following?(user1, user3)
880
881 {:ok, user2} = User.follow(user2, user3)
882 assert User.following?(user2, user3)
883
884 {:ok, user3} = User.follow(user3, user2)
885 assert User.following?(user3, user2)
886
887 {:ok, public_activity} = CommonAPI.post(user3, %{"status" => "hi 1"})
888
889 {:ok, private_activity_1} =
890 CommonAPI.post(user3, %{"status" => "hi 2", "visibility" => "private"})
891
892 {:ok, private_activity_2} =
893 CommonAPI.post(user2, %{
894 "status" => "hi 3",
895 "visibility" => "private",
896 "in_reply_to_status_id" => private_activity_1.id
897 })
898
899 {:ok, private_activity_3} =
900 CommonAPI.post(user3, %{
901 "status" => "hi 4",
902 "visibility" => "private",
903 "in_reply_to_status_id" => private_activity_2.id
904 })
905
906 activities = ActivityPub.fetch_activities([user1.ap_id | user1.following])
907
908 private_activity_1 = Activity.get_by_ap_id_with_object(private_activity_1.data["id"])
909 assert [public_activity, private_activity_1, private_activity_3] == activities
910 assert length(activities) == 3
911
912 activities = ActivityPub.contain_timeline(activities, user1)
913
914 assert [public_activity, private_activity_1] == activities
915 assert length(activities) == 2
916 end
917 end
918
919 test "it can fetch plume articles" do
920 {:ok, object} =
921 ActivityPub.fetch_object_from_id(
922 "https://baptiste.gelez.xyz/~/PlumeDevelopment/this-month-in-plume-june-2018/"
923 )
924
925 assert object
926 end
927
928 describe "update" do
929 test "it creates an update activity with the new user data" do
930 user = insert(:user)
931 {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user)
932 user_data = Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user})
933
934 {:ok, update} =
935 ActivityPub.update(%{
936 actor: user_data["id"],
937 to: [user.follower_address],
938 cc: [],
939 object: user_data
940 })
941
942 assert update.data["actor"] == user.ap_id
943 assert update.data["to"] == [user.follower_address]
944 assert update.data["object"]["id"] == user_data["id"]
945 assert update.data["object"]["type"] == user_data["type"]
946 end
947 end
948
949 test "it can fetch peertube videos" do
950 {:ok, object} =
951 ActivityPub.fetch_object_from_id(
952 "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
953 )
954
955 assert object
956 end
957
958 test "returned pinned statuses" do
959 Pleroma.Config.put([:instance, :max_pinned_statuses], 3)
960 user = insert(:user)
961
962 {:ok, activity_one} = CommonAPI.post(user, %{"status" => "HI!!!"})
963 {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
964 {:ok, activity_three} = CommonAPI.post(user, %{"status" => "HI!!!"})
965
966 CommonAPI.pin(activity_one.id, user)
967 user = refresh_record(user)
968
969 CommonAPI.pin(activity_two.id, user)
970 user = refresh_record(user)
971
972 CommonAPI.pin(activity_three.id, user)
973 user = refresh_record(user)
974
975 activities = ActivityPub.fetch_user_activities(user, nil, %{"pinned" => "true"})
976
977 assert 3 = length(activities)
978 end
979
980 test "it can create a Flag activity" do
981 reporter = insert(:user)
982 target_account = insert(:user)
983 {:ok, activity} = CommonAPI.post(target_account, %{"status" => "foobar"})
984 context = Utils.generate_context_id()
985 content = "foobar"
986
987 reporter_ap_id = reporter.ap_id
988 target_ap_id = target_account.ap_id
989 activity_ap_id = activity.data["id"]
990
991 assert {:ok, activity} =
992 ActivityPub.flag(%{
993 actor: reporter,
994 context: context,
995 account: target_account,
996 statuses: [activity],
997 content: content
998 })
999
1000 assert %Activity{
1001 actor: ^reporter_ap_id,
1002 data: %{
1003 "type" => "Flag",
1004 "content" => ^content,
1005 "context" => ^context,
1006 "object" => [^target_ap_id, ^activity_ap_id]
1007 }
1008 } = activity
1009 end
1010
1011 describe "publish_one/1" do
1012 test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is not specified",
1013 Instances,
1014 [:passthrough],
1015 [] do
1016 actor = insert(:user)
1017 inbox = "http://200.site/users/nick1/inbox"
1018
1019 assert {:ok, _} = ActivityPub.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
1020
1021 assert called(Instances.set_reachable(inbox))
1022 end
1023
1024 test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is set",
1025 Instances,
1026 [:passthrough],
1027 [] do
1028 actor = insert(:user)
1029 inbox = "http://200.site/users/nick1/inbox"
1030
1031 assert {:ok, _} =
1032 ActivityPub.publish_one(%{
1033 inbox: inbox,
1034 json: "{}",
1035 actor: actor,
1036 id: 1,
1037 unreachable_since: NaiveDateTime.utc_now()
1038 })
1039
1040 assert called(Instances.set_reachable(inbox))
1041 end
1042
1043 test_with_mock "does NOT call `Instances.set_reachable` on successful federation if `unreachable_since` is nil",
1044 Instances,
1045 [:passthrough],
1046 [] do
1047 actor = insert(:user)
1048 inbox = "http://200.site/users/nick1/inbox"
1049
1050 assert {:ok, _} =
1051 ActivityPub.publish_one(%{
1052 inbox: inbox,
1053 json: "{}",
1054 actor: actor,
1055 id: 1,
1056 unreachable_since: nil
1057 })
1058
1059 refute called(Instances.set_reachable(inbox))
1060 end
1061
1062 test_with_mock "calls `Instances.set_unreachable` on target inbox on non-2xx HTTP response code",
1063 Instances,
1064 [:passthrough],
1065 [] do
1066 actor = insert(:user)
1067 inbox = "http://404.site/users/nick1/inbox"
1068
1069 assert {:error, _} =
1070 ActivityPub.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
1071
1072 assert called(Instances.set_unreachable(inbox))
1073 end
1074
1075 test_with_mock "it calls `Instances.set_unreachable` on target inbox on request error of any kind",
1076 Instances,
1077 [:passthrough],
1078 [] do
1079 actor = insert(:user)
1080 inbox = "http://connrefused.site/users/nick1/inbox"
1081
1082 assert {:error, _} =
1083 ActivityPub.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
1084
1085 assert called(Instances.set_unreachable(inbox))
1086 end
1087
1088 test_with_mock "does NOT call `Instances.set_unreachable` if target is reachable",
1089 Instances,
1090 [:passthrough],
1091 [] do
1092 actor = insert(:user)
1093 inbox = "http://200.site/users/nick1/inbox"
1094
1095 assert {:ok, _} = ActivityPub.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
1096
1097 refute called(Instances.set_unreachable(inbox))
1098 end
1099
1100 test_with_mock "does NOT call `Instances.set_unreachable` if target instance has non-nil `unreachable_since`",
1101 Instances,
1102 [:passthrough],
1103 [] do
1104 actor = insert(:user)
1105 inbox = "http://connrefused.site/users/nick1/inbox"
1106
1107 assert {:error, _} =
1108 ActivityPub.publish_one(%{
1109 inbox: inbox,
1110 json: "{}",
1111 actor: actor,
1112 id: 1,
1113 unreachable_since: NaiveDateTime.utc_now()
1114 })
1115
1116 refute called(Instances.set_unreachable(inbox))
1117 end
1118 end
1119
1120 def data_uri do
1121 File.read!("test/fixtures/avatar_data_uri")
1122 end
1123 end