Merge remote-tracking branch 'remotes/upstream/develop' into 1149-oban-job-queue
[akkoma] / test / web / activity_pub / activity_pub_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
6 use Pleroma.DataCase
7 alias Pleroma.Activity
8 alias Pleroma.Builders.ActivityBuilder
9 alias Pleroma.Object
10 alias Pleroma.User
11 alias Pleroma.Web.ActivityPub.ActivityPub
12 alias Pleroma.Web.ActivityPub.Utils
13 alias Pleroma.Web.CommonAPI
14
15 import Pleroma.Factory
16 import Tesla.Mock
17 import Mock
18
19 setup do
20 mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
21 :ok
22 end
23
24 clear_config([:instance, :federating])
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 assert object = Object.normalize(activity)
258 assert is_binary(object.data["id"])
259 end
260 end
261
262 describe "create activities" do
263 test "removes doubled 'to' recipients" do
264 user = insert(:user)
265
266 {:ok, activity} =
267 ActivityPub.create(%{
268 to: ["user1", "user1", "user2"],
269 actor: user,
270 context: "",
271 object: %{
272 "to" => ["user1", "user1", "user2"],
273 "type" => "Note",
274 "content" => "testing"
275 }
276 })
277
278 assert activity.data["to"] == ["user1", "user2"]
279 assert activity.actor == user.ap_id
280 assert activity.recipients == ["user1", "user2", user.ap_id]
281 end
282
283 test "increases user note count only for public activities" do
284 user = insert(:user)
285
286 {:ok, _} =
287 CommonAPI.post(User.get_cached_by_id(user.id), %{
288 "status" => "1",
289 "visibility" => "public"
290 })
291
292 {:ok, _} =
293 CommonAPI.post(User.get_cached_by_id(user.id), %{
294 "status" => "2",
295 "visibility" => "unlisted"
296 })
297
298 {:ok, _} =
299 CommonAPI.post(User.get_cached_by_id(user.id), %{
300 "status" => "2",
301 "visibility" => "private"
302 })
303
304 {:ok, _} =
305 CommonAPI.post(User.get_cached_by_id(user.id), %{
306 "status" => "3",
307 "visibility" => "direct"
308 })
309
310 user = User.get_cached_by_id(user.id)
311 assert user.info.note_count == 2
312 end
313
314 test "increases replies count" do
315 user = insert(:user)
316 user2 = insert(:user)
317
318 {:ok, activity} = CommonAPI.post(user, %{"status" => "1", "visibility" => "public"})
319 ap_id = activity.data["id"]
320 reply_data = %{"status" => "1", "in_reply_to_status_id" => activity.id}
321
322 # public
323 {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "public"))
324 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
325 assert object.data["repliesCount"] == 1
326
327 # unlisted
328 {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "unlisted"))
329 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
330 assert object.data["repliesCount"] == 2
331
332 # private
333 {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "private"))
334 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
335 assert object.data["repliesCount"] == 2
336
337 # direct
338 {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "direct"))
339 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
340 assert object.data["repliesCount"] == 2
341 end
342 end
343
344 describe "fetch activities for recipients" do
345 test "retrieve the activities for certain recipients" do
346 {:ok, activity_one} = ActivityBuilder.insert(%{"to" => ["someone"]})
347 {:ok, activity_two} = ActivityBuilder.insert(%{"to" => ["someone_else"]})
348 {:ok, _activity_three} = ActivityBuilder.insert(%{"to" => ["noone"]})
349
350 activities = ActivityPub.fetch_activities(["someone", "someone_else"])
351 assert length(activities) == 2
352 assert activities == [activity_one, activity_two]
353 end
354 end
355
356 describe "fetch activities in context" do
357 test "retrieves activities that have a given context" do
358 {:ok, activity} = ActivityBuilder.insert(%{"type" => "Create", "context" => "2hu"})
359 {:ok, activity_two} = ActivityBuilder.insert(%{"type" => "Create", "context" => "2hu"})
360 {:ok, _activity_three} = ActivityBuilder.insert(%{"type" => "Create", "context" => "3hu"})
361 {:ok, _activity_four} = ActivityBuilder.insert(%{"type" => "Announce", "context" => "2hu"})
362 activity_five = insert(:note_activity)
363 user = insert(:user)
364
365 {:ok, user} = User.block(user, %{ap_id: activity_five.data["actor"]})
366
367 activities = ActivityPub.fetch_activities_for_context("2hu", %{"blocking_user" => user})
368 assert activities == [activity_two, activity]
369 end
370 end
371
372 test "doesn't return blocked activities" do
373 activity_one = insert(:note_activity)
374 activity_two = insert(:note_activity)
375 activity_three = insert(:note_activity)
376 user = insert(:user)
377 booster = insert(:user)
378 {:ok, user} = User.block(user, %{ap_id: activity_one.data["actor"]})
379
380 activities =
381 ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
382
383 assert Enum.member?(activities, activity_two)
384 assert Enum.member?(activities, activity_three)
385 refute Enum.member?(activities, activity_one)
386
387 {:ok, user} = User.unblock(user, %{ap_id: activity_one.data["actor"]})
388
389 activities =
390 ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
391
392 assert Enum.member?(activities, activity_two)
393 assert Enum.member?(activities, activity_three)
394 assert Enum.member?(activities, activity_one)
395
396 {:ok, user} = User.block(user, %{ap_id: activity_three.data["actor"]})
397 {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster)
398 %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
399 activity_three = Activity.get_by_id(activity_three.id)
400
401 activities =
402 ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
403
404 assert Enum.member?(activities, activity_two)
405 refute Enum.member?(activities, activity_three)
406 refute Enum.member?(activities, boost_activity)
407 assert Enum.member?(activities, activity_one)
408
409 activities =
410 ActivityPub.fetch_activities([], %{"blocking_user" => nil, "skip_preload" => true})
411
412 assert Enum.member?(activities, activity_two)
413 assert Enum.member?(activities, activity_three)
414 assert Enum.member?(activities, boost_activity)
415 assert Enum.member?(activities, activity_one)
416 end
417
418 test "doesn't return transitive interactions concerning blocked users" do
419 blocker = insert(:user)
420 blockee = insert(:user)
421 friend = insert(:user)
422
423 {:ok, blocker} = User.block(blocker, blockee)
424
425 {:ok, activity_one} = CommonAPI.post(friend, %{"status" => "hey!"})
426
427 {:ok, activity_two} = CommonAPI.post(friend, %{"status" => "hey! @#{blockee.nickname}"})
428
429 {:ok, activity_three} = CommonAPI.post(blockee, %{"status" => "hey! @#{friend.nickname}"})
430
431 {:ok, activity_four} = CommonAPI.post(blockee, %{"status" => "hey! @#{blocker.nickname}"})
432
433 activities = ActivityPub.fetch_activities([], %{"blocking_user" => blocker})
434
435 assert Enum.member?(activities, activity_one)
436 refute Enum.member?(activities, activity_two)
437 refute Enum.member?(activities, activity_three)
438 refute Enum.member?(activities, activity_four)
439 end
440
441 test "doesn't return announce activities concerning blocked users" do
442 blocker = insert(:user)
443 blockee = insert(:user)
444 friend = insert(:user)
445
446 {:ok, blocker} = User.block(blocker, blockee)
447
448 {:ok, activity_one} = CommonAPI.post(friend, %{"status" => "hey!"})
449
450 {:ok, activity_two} = CommonAPI.post(blockee, %{"status" => "hey! @#{friend.nickname}"})
451
452 {:ok, activity_three, _} = CommonAPI.repeat(activity_two.id, friend)
453
454 activities =
455 ActivityPub.fetch_activities([], %{"blocking_user" => blocker})
456 |> Enum.map(fn act -> act.id end)
457
458 assert Enum.member?(activities, activity_one.id)
459 refute Enum.member?(activities, activity_two.id)
460 refute Enum.member?(activities, activity_three.id)
461 end
462
463 test "doesn't return activities from blocked domains" do
464 domain = "dogwhistle.zone"
465 domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"})
466 note = insert(:note, %{data: %{"actor" => domain_user.ap_id}})
467 activity = insert(:note_activity, %{note: note})
468 user = insert(:user)
469 {:ok, user} = User.block_domain(user, domain)
470
471 activities =
472 ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
473
474 refute activity in activities
475
476 followed_user = insert(:user)
477 ActivityPub.follow(user, followed_user)
478 {:ok, repeat_activity, _} = CommonAPI.repeat(activity.id, followed_user)
479
480 activities =
481 ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})
482
483 refute repeat_activity in activities
484 end
485
486 test "doesn't return muted activities" do
487 activity_one = insert(:note_activity)
488 activity_two = insert(:note_activity)
489 activity_three = insert(:note_activity)
490 user = insert(:user)
491 booster = insert(:user)
492 {:ok, user} = User.mute(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 refute Enum.member?(activities, activity_one)
500
501 # Calling with 'with_muted' will deliver muted activities, too.
502 activities =
503 ActivityPub.fetch_activities([], %{
504 "muting_user" => user,
505 "with_muted" => true,
506 "skip_preload" => true
507 })
508
509 assert Enum.member?(activities, activity_two)
510 assert Enum.member?(activities, activity_three)
511 assert Enum.member?(activities, activity_one)
512
513 {:ok, user} = User.unmute(user, %User{ap_id: activity_one.data["actor"]})
514
515 activities =
516 ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true})
517
518 assert Enum.member?(activities, activity_two)
519 assert Enum.member?(activities, activity_three)
520 assert Enum.member?(activities, activity_one)
521
522 {:ok, user} = User.mute(user, %User{ap_id: activity_three.data["actor"]})
523 {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster)
524 %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)
525 activity_three = Activity.get_by_id(activity_three.id)
526
527 activities =
528 ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true})
529
530 assert Enum.member?(activities, activity_two)
531 refute Enum.member?(activities, activity_three)
532 refute Enum.member?(activities, boost_activity)
533 assert Enum.member?(activities, activity_one)
534
535 activities = ActivityPub.fetch_activities([], %{"muting_user" => nil, "skip_preload" => true})
536
537 assert Enum.member?(activities, activity_two)
538 assert Enum.member?(activities, activity_three)
539 assert Enum.member?(activities, boost_activity)
540 assert Enum.member?(activities, activity_one)
541 end
542
543 test "doesn't return thread muted activities" do
544 user = insert(:user)
545 _activity_one = insert(:note_activity)
546 note_two = insert(:note, data: %{"context" => "suya.."})
547 activity_two = insert(:note_activity, note: note_two)
548
549 {:ok, _activity_two} = CommonAPI.add_mute(user, activity_two)
550
551 assert [_activity_one] = ActivityPub.fetch_activities([], %{"muting_user" => user})
552 end
553
554 test "returns thread muted activities when with_muted is set" do
555 user = insert(:user)
556 _activity_one = insert(:note_activity)
557 note_two = insert(:note, data: %{"context" => "suya.."})
558 activity_two = insert(:note_activity, note: note_two)
559
560 {:ok, _activity_two} = CommonAPI.add_mute(user, activity_two)
561
562 assert [_activity_two, _activity_one] =
563 ActivityPub.fetch_activities([], %{"muting_user" => user, "with_muted" => true})
564 end
565
566 test "does include announces on request" do
567 activity_three = insert(:note_activity)
568 user = insert(:user)
569 booster = insert(:user)
570
571 {:ok, user} = User.follow(user, booster)
572
573 {:ok, announce, _object} = CommonAPI.repeat(activity_three.id, booster)
574
575 [announce_activity] = ActivityPub.fetch_activities([user.ap_id | user.following])
576
577 assert announce_activity.id == announce.id
578 end
579
580 test "excludes reblogs on request" do
581 user = insert(:user)
582 {:ok, expected_activity} = ActivityBuilder.insert(%{"type" => "Create"}, %{:user => user})
583 {:ok, _} = ActivityBuilder.insert(%{"type" => "Announce"}, %{:user => user})
584
585 [activity] = ActivityPub.fetch_user_activities(user, nil, %{"exclude_reblogs" => "true"})
586
587 assert activity == expected_activity
588 end
589
590 describe "public fetch activities" do
591 test "doesn't retrieve unlisted activities" do
592 user = insert(:user)
593
594 {:ok, _unlisted_activity} =
595 CommonAPI.post(user, %{"status" => "yeah", "visibility" => "unlisted"})
596
597 {:ok, listed_activity} = CommonAPI.post(user, %{"status" => "yeah"})
598
599 [activity] = ActivityPub.fetch_public_activities()
600
601 assert activity == listed_activity
602 end
603
604 test "retrieves public activities" do
605 _activities = ActivityPub.fetch_public_activities()
606
607 %{public: public} = ActivityBuilder.public_and_non_public()
608
609 activities = ActivityPub.fetch_public_activities()
610 assert length(activities) == 1
611 assert Enum.at(activities, 0) == public
612 end
613
614 test "retrieves a maximum of 20 activities" do
615 activities = ActivityBuilder.insert_list(30)
616 last_expected = List.last(activities)
617
618 activities = ActivityPub.fetch_public_activities()
619 last = List.last(activities)
620
621 assert length(activities) == 20
622 assert last == last_expected
623 end
624
625 test "retrieves ids starting from a since_id" do
626 activities = ActivityBuilder.insert_list(30)
627 later_activities = ActivityBuilder.insert_list(10)
628 since_id = List.last(activities).id
629 last_expected = List.last(later_activities)
630
631 activities = ActivityPub.fetch_public_activities(%{"since_id" => since_id})
632 last = List.last(activities)
633
634 assert length(activities) == 10
635 assert last == last_expected
636 end
637
638 test "retrieves ids up to max_id" do
639 _first_activities = ActivityBuilder.insert_list(10)
640 activities = ActivityBuilder.insert_list(20)
641 later_activities = ActivityBuilder.insert_list(10)
642 max_id = List.first(later_activities).id
643 last_expected = List.last(activities)
644
645 activities = ActivityPub.fetch_public_activities(%{"max_id" => max_id})
646 last = List.last(activities)
647
648 assert length(activities) == 20
649 assert last == last_expected
650 end
651
652 test "doesn't return reblogs for users for whom reblogs have been muted" do
653 activity = insert(:note_activity)
654 user = insert(:user)
655 booster = insert(:user)
656 {:ok, user} = CommonAPI.hide_reblogs(user, booster)
657
658 {:ok, activity, _} = CommonAPI.repeat(activity.id, booster)
659
660 activities = ActivityPub.fetch_activities([], %{"muting_user" => user})
661
662 refute Enum.any?(activities, fn %{id: id} -> id == activity.id end)
663 end
664
665 test "returns reblogs for users for whom reblogs have not been muted" do
666 activity = insert(:note_activity)
667 user = insert(:user)
668 booster = insert(:user)
669 {:ok, user} = CommonAPI.hide_reblogs(user, booster)
670 {:ok, user} = CommonAPI.show_reblogs(user, booster)
671
672 {:ok, activity, _} = CommonAPI.repeat(activity.id, booster)
673
674 activities = ActivityPub.fetch_activities([], %{"muting_user" => user})
675
676 assert Enum.any?(activities, fn %{id: id} -> id == activity.id end)
677 end
678 end
679
680 describe "like an object" do
681 test_with_mock "sends an activity to federation", Pleroma.Web.Federator, [:passthrough], [] do
682 Pleroma.Config.put([:instance, :federating], true)
683 note_activity = insert(:note_activity)
684 assert object_activity = Object.normalize(note_activity)
685
686 user = insert(:user)
687
688 {:ok, like_activity, _object} = ActivityPub.like(user, object_activity)
689 assert called(Pleroma.Web.Federator.publish(like_activity))
690 end
691
692 test "returns exist activity if object already liked" do
693 note_activity = insert(:note_activity)
694 assert object_activity = Object.normalize(note_activity)
695
696 user = insert(:user)
697
698 {:ok, like_activity, _object} = ActivityPub.like(user, object_activity)
699
700 {:ok, like_activity_exist, _object} = ActivityPub.like(user, object_activity)
701 assert like_activity == like_activity_exist
702 end
703
704 test "adds a like activity to the db" do
705 note_activity = insert(:note_activity)
706 assert object = Object.normalize(note_activity)
707
708 user = insert(:user)
709 user_two = insert(:user)
710
711 {:ok, like_activity, object} = ActivityPub.like(user, object)
712
713 assert like_activity.data["actor"] == user.ap_id
714 assert like_activity.data["type"] == "Like"
715 assert like_activity.data["object"] == object.data["id"]
716 assert like_activity.data["to"] == [User.ap_followers(user), note_activity.data["actor"]]
717 assert like_activity.data["context"] == object.data["context"]
718 assert object.data["like_count"] == 1
719 assert object.data["likes"] == [user.ap_id]
720
721 # Just return the original activity if the user already liked it.
722 {:ok, same_like_activity, object} = ActivityPub.like(user, object)
723
724 assert like_activity == same_like_activity
725 assert object.data["likes"] == [user.ap_id]
726 assert object.data["like_count"] == 1
727
728 {:ok, _like_activity, object} = ActivityPub.like(user_two, object)
729 assert object.data["like_count"] == 2
730 end
731 end
732
733 describe "unliking" do
734 test_with_mock "sends an activity to federation", Pleroma.Web.Federator, [:passthrough], [] do
735 Pleroma.Config.put([:instance, :federating], true)
736
737 note_activity = insert(:note_activity)
738 object = Object.normalize(note_activity)
739 user = insert(:user)
740
741 {:ok, object} = ActivityPub.unlike(user, object)
742 refute called(Pleroma.Web.Federator.publish())
743
744 {:ok, _like_activity, object} = ActivityPub.like(user, object)
745 assert object.data["like_count"] == 1
746
747 {:ok, unlike_activity, _, object} = ActivityPub.unlike(user, object)
748 assert object.data["like_count"] == 0
749
750 assert called(Pleroma.Web.Federator.publish(unlike_activity))
751 end
752
753 test "unliking a previously liked object" do
754 note_activity = insert(:note_activity)
755 object = Object.normalize(note_activity)
756 user = insert(:user)
757
758 # Unliking something that hasn't been liked does nothing
759 {:ok, object} = ActivityPub.unlike(user, object)
760 assert object.data["like_count"] == 0
761
762 {:ok, like_activity, object} = ActivityPub.like(user, object)
763 assert object.data["like_count"] == 1
764
765 {:ok, _, _, object} = ActivityPub.unlike(user, object)
766 assert object.data["like_count"] == 0
767
768 assert Activity.get_by_id(like_activity.id) == nil
769 end
770 end
771
772 describe "announcing an object" do
773 test "adds an announce activity to the db" do
774 note_activity = insert(:note_activity)
775 object = Object.normalize(note_activity)
776 user = insert(:user)
777
778 {:ok, announce_activity, object} = ActivityPub.announce(user, object)
779 assert object.data["announcement_count"] == 1
780 assert object.data["announcements"] == [user.ap_id]
781
782 assert announce_activity.data["to"] == [
783 User.ap_followers(user),
784 note_activity.data["actor"]
785 ]
786
787 assert announce_activity.data["object"] == object.data["id"]
788 assert announce_activity.data["actor"] == user.ap_id
789 assert announce_activity.data["context"] == object.data["context"]
790 end
791 end
792
793 describe "unannouncing an object" do
794 test "unannouncing a previously announced object" do
795 note_activity = insert(:note_activity)
796 object = Object.normalize(note_activity)
797 user = insert(:user)
798
799 # Unannouncing an object that is not announced does nothing
800 # {:ok, object} = ActivityPub.unannounce(user, object)
801 # assert object.data["announcement_count"] == 0
802
803 {:ok, announce_activity, object} = ActivityPub.announce(user, object)
804 assert object.data["announcement_count"] == 1
805
806 {:ok, unannounce_activity, object} = ActivityPub.unannounce(user, object)
807 assert object.data["announcement_count"] == 0
808
809 assert unannounce_activity.data["to"] == [
810 User.ap_followers(user),
811 announce_activity.data["actor"]
812 ]
813
814 assert unannounce_activity.data["type"] == "Undo"
815 assert unannounce_activity.data["object"] == announce_activity.data
816 assert unannounce_activity.data["actor"] == user.ap_id
817 assert unannounce_activity.data["context"] == announce_activity.data["context"]
818
819 assert Activity.get_by_id(announce_activity.id) == nil
820 end
821 end
822
823 describe "uploading files" do
824 test "copies the file to the configured folder" do
825 file = %Plug.Upload{
826 content_type: "image/jpg",
827 path: Path.absname("test/fixtures/image.jpg"),
828 filename: "an_image.jpg"
829 }
830
831 {:ok, %Object{} = object} = ActivityPub.upload(file)
832 assert object.data["name"] == "an_image.jpg"
833 end
834
835 test "works with base64 encoded images" do
836 file = %{
837 "img" => data_uri()
838 }
839
840 {:ok, %Object{}} = ActivityPub.upload(file)
841 end
842 end
843
844 describe "fetch the latest Follow" do
845 test "fetches the latest Follow activity" do
846 %Activity{data: %{"type" => "Follow"}} = activity = insert(:follow_activity)
847 follower = Repo.get_by(User, ap_id: activity.data["actor"])
848 followed = Repo.get_by(User, ap_id: activity.data["object"])
849
850 assert activity == Utils.fetch_latest_follow(follower, followed)
851 end
852 end
853
854 describe "following / unfollowing" do
855 test "creates a follow activity" do
856 follower = insert(:user)
857 followed = insert(:user)
858
859 {:ok, activity} = ActivityPub.follow(follower, followed)
860 assert activity.data["type"] == "Follow"
861 assert activity.data["actor"] == follower.ap_id
862 assert activity.data["object"] == followed.ap_id
863 end
864
865 test "creates an undo activity for the last follow" do
866 follower = insert(:user)
867 followed = insert(:user)
868
869 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
870 {:ok, activity} = ActivityPub.unfollow(follower, followed)
871
872 assert activity.data["type"] == "Undo"
873 assert activity.data["actor"] == follower.ap_id
874
875 embedded_object = activity.data["object"]
876 assert is_map(embedded_object)
877 assert embedded_object["type"] == "Follow"
878 assert embedded_object["object"] == followed.ap_id
879 assert embedded_object["id"] == follow_activity.data["id"]
880 end
881 end
882
883 describe "blocking / unblocking" do
884 test "creates a block activity" do
885 blocker = insert(:user)
886 blocked = insert(:user)
887
888 {:ok, activity} = ActivityPub.block(blocker, blocked)
889
890 assert activity.data["type"] == "Block"
891 assert activity.data["actor"] == blocker.ap_id
892 assert activity.data["object"] == blocked.ap_id
893 end
894
895 test "creates an undo activity for the last block" do
896 blocker = insert(:user)
897 blocked = insert(:user)
898
899 {:ok, block_activity} = ActivityPub.block(blocker, blocked)
900 {:ok, activity} = ActivityPub.unblock(blocker, blocked)
901
902 assert activity.data["type"] == "Undo"
903 assert activity.data["actor"] == blocker.ap_id
904
905 embedded_object = activity.data["object"]
906 assert is_map(embedded_object)
907 assert embedded_object["type"] == "Block"
908 assert embedded_object["object"] == blocked.ap_id
909 assert embedded_object["id"] == block_activity.data["id"]
910 end
911 end
912
913 describe "deletion" do
914 test "it creates a delete activity and deletes the original object" do
915 note = insert(:note_activity)
916 object = Object.normalize(note)
917 {:ok, delete} = ActivityPub.delete(object)
918
919 assert delete.data["type"] == "Delete"
920 assert delete.data["actor"] == note.data["actor"]
921 assert delete.data["object"] == object.data["id"]
922
923 assert Activity.get_by_id(delete.id) != nil
924
925 assert Repo.get(Object, object.id).data["type"] == "Tombstone"
926 end
927
928 test "decrements user note count only for public activities" do
929 user = insert(:user, info: %{note_count: 10})
930
931 {:ok, a1} =
932 CommonAPI.post(User.get_cached_by_id(user.id), %{
933 "status" => "yeah",
934 "visibility" => "public"
935 })
936
937 {:ok, a2} =
938 CommonAPI.post(User.get_cached_by_id(user.id), %{
939 "status" => "yeah",
940 "visibility" => "unlisted"
941 })
942
943 {:ok, a3} =
944 CommonAPI.post(User.get_cached_by_id(user.id), %{
945 "status" => "yeah",
946 "visibility" => "private"
947 })
948
949 {:ok, a4} =
950 CommonAPI.post(User.get_cached_by_id(user.id), %{
951 "status" => "yeah",
952 "visibility" => "direct"
953 })
954
955 {:ok, _} = Object.normalize(a1) |> ActivityPub.delete()
956 {:ok, _} = Object.normalize(a2) |> ActivityPub.delete()
957 {:ok, _} = Object.normalize(a3) |> ActivityPub.delete()
958 {:ok, _} = Object.normalize(a4) |> ActivityPub.delete()
959
960 user = User.get_cached_by_id(user.id)
961 assert user.info.note_count == 10
962 end
963
964 test "it creates a delete activity and checks that it is also sent to users mentioned by the deleted object" do
965 user = insert(:user)
966 note = insert(:note_activity)
967 object = Object.normalize(note)
968
969 {:ok, object} =
970 object
971 |> Object.change(%{
972 data: %{
973 "actor" => object.data["actor"],
974 "id" => object.data["id"],
975 "to" => [user.ap_id],
976 "type" => "Note"
977 }
978 })
979 |> Object.update_and_set_cache()
980
981 {:ok, delete} = ActivityPub.delete(object)
982
983 assert user.ap_id in delete.data["to"]
984 end
985
986 test "decreases reply count" do
987 user = insert(:user)
988 user2 = insert(:user)
989
990 {:ok, activity} = CommonAPI.post(user, %{"status" => "1", "visibility" => "public"})
991 reply_data = %{"status" => "1", "in_reply_to_status_id" => activity.id}
992 ap_id = activity.data["id"]
993
994 {:ok, public_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "public"))
995 {:ok, unlisted_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "unlisted"))
996 {:ok, private_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "private"))
997 {:ok, direct_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "direct"))
998
999 _ = CommonAPI.delete(direct_reply.id, user2)
1000 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
1001 assert object.data["repliesCount"] == 2
1002
1003 _ = CommonAPI.delete(private_reply.id, user2)
1004 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
1005 assert object.data["repliesCount"] == 2
1006
1007 _ = CommonAPI.delete(public_reply.id, user2)
1008 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
1009 assert object.data["repliesCount"] == 1
1010
1011 _ = CommonAPI.delete(unlisted_reply.id, user2)
1012 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
1013 assert object.data["repliesCount"] == 0
1014 end
1015 end
1016
1017 describe "timeline post-processing" do
1018 test "it filters broken threads" do
1019 user1 = insert(:user)
1020 user2 = insert(:user)
1021 user3 = insert(:user)
1022
1023 {:ok, user1} = User.follow(user1, user3)
1024 assert User.following?(user1, user3)
1025
1026 {:ok, user2} = User.follow(user2, user3)
1027 assert User.following?(user2, user3)
1028
1029 {:ok, user3} = User.follow(user3, user2)
1030 assert User.following?(user3, user2)
1031
1032 {:ok, public_activity} = CommonAPI.post(user3, %{"status" => "hi 1"})
1033
1034 {:ok, private_activity_1} =
1035 CommonAPI.post(user3, %{"status" => "hi 2", "visibility" => "private"})
1036
1037 {:ok, private_activity_2} =
1038 CommonAPI.post(user2, %{
1039 "status" => "hi 3",
1040 "visibility" => "private",
1041 "in_reply_to_status_id" => private_activity_1.id
1042 })
1043
1044 {:ok, private_activity_3} =
1045 CommonAPI.post(user3, %{
1046 "status" => "hi 4",
1047 "visibility" => "private",
1048 "in_reply_to_status_id" => private_activity_2.id
1049 })
1050
1051 activities =
1052 ActivityPub.fetch_activities([user1.ap_id | user1.following])
1053 |> Enum.map(fn a -> a.id end)
1054
1055 private_activity_1 = Activity.get_by_ap_id_with_object(private_activity_1.data["id"])
1056
1057 assert [public_activity.id, private_activity_1.id, private_activity_3.id] == activities
1058
1059 assert length(activities) == 3
1060
1061 activities =
1062 ActivityPub.fetch_activities([user1.ap_id | user1.following], %{"user" => user1})
1063 |> Enum.map(fn a -> a.id end)
1064
1065 assert [public_activity.id, private_activity_1.id] == activities
1066 assert length(activities) == 2
1067 end
1068 end
1069
1070 describe "update" do
1071 test "it creates an update activity with the new user data" do
1072 user = insert(:user)
1073 {:ok, user} = User.ensure_keys_present(user)
1074 user_data = Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user})
1075
1076 {:ok, update} =
1077 ActivityPub.update(%{
1078 actor: user_data["id"],
1079 to: [user.follower_address],
1080 cc: [],
1081 object: user_data
1082 })
1083
1084 assert update.data["actor"] == user.ap_id
1085 assert update.data["to"] == [user.follower_address]
1086 assert embedded_object = update.data["object"]
1087 assert embedded_object["id"] == user_data["id"]
1088 assert embedded_object["type"] == user_data["type"]
1089 end
1090 end
1091
1092 test "returned pinned statuses" do
1093 Pleroma.Config.put([:instance, :max_pinned_statuses], 3)
1094 user = insert(:user)
1095
1096 {:ok, activity_one} = CommonAPI.post(user, %{"status" => "HI!!!"})
1097 {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
1098 {:ok, activity_three} = CommonAPI.post(user, %{"status" => "HI!!!"})
1099
1100 CommonAPI.pin(activity_one.id, user)
1101 user = refresh_record(user)
1102
1103 CommonAPI.pin(activity_two.id, user)
1104 user = refresh_record(user)
1105
1106 CommonAPI.pin(activity_three.id, user)
1107 user = refresh_record(user)
1108
1109 activities = ActivityPub.fetch_user_activities(user, nil, %{"pinned" => "true"})
1110
1111 assert 3 = length(activities)
1112 end
1113
1114 test "it can create a Flag activity" do
1115 reporter = insert(:user)
1116 target_account = insert(:user)
1117 {:ok, activity} = CommonAPI.post(target_account, %{"status" => "foobar"})
1118 context = Utils.generate_context_id()
1119 content = "foobar"
1120
1121 reporter_ap_id = reporter.ap_id
1122 target_ap_id = target_account.ap_id
1123 activity_ap_id = activity.data["id"]
1124
1125 assert {:ok, activity} =
1126 ActivityPub.flag(%{
1127 actor: reporter,
1128 context: context,
1129 account: target_account,
1130 statuses: [activity],
1131 content: content
1132 })
1133
1134 assert %Activity{
1135 actor: ^reporter_ap_id,
1136 data: %{
1137 "type" => "Flag",
1138 "content" => ^content,
1139 "context" => ^context,
1140 "object" => [^target_ap_id, ^activity_ap_id]
1141 }
1142 } = activity
1143 end
1144
1145 test "fetch_activities/2 returns activities addressed to a list " do
1146 user = insert(:user)
1147 member = insert(:user)
1148 {:ok, list} = Pleroma.List.create("foo", user)
1149 {:ok, list} = Pleroma.List.follow(list, member)
1150
1151 {:ok, activity} =
1152 CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"})
1153
1154 activity = Repo.preload(activity, :bookmark)
1155 activity = %Activity{activity | thread_muted?: !!activity.thread_muted?}
1156
1157 assert ActivityPub.fetch_activities([], %{"user" => user}) == [activity]
1158 end
1159
1160 def data_uri do
1161 File.read!("test/fixtures/avatar_data_uri")
1162 end
1163
1164 describe "fetch_activities_bounded" do
1165 test "fetches private posts for followed users" do
1166 user = insert(:user)
1167
1168 {:ok, activity} =
1169 CommonAPI.post(user, %{
1170 "status" => "thought I looked cute might delete later :3",
1171 "visibility" => "private"
1172 })
1173
1174 [result] = ActivityPub.fetch_activities_bounded([user.follower_address], [])
1175 assert result.id == activity.id
1176 end
1177
1178 test "fetches only public posts for other users" do
1179 user = insert(:user)
1180 {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe", "visibility" => "public"})
1181
1182 {:ok, _private_activity} =
1183 CommonAPI.post(user, %{
1184 "status" => "why is tenshi eating a corndog so cute?",
1185 "visibility" => "private"
1186 })
1187
1188 [result] = ActivityPub.fetch_activities_bounded([], [user.follower_address])
1189 assert result.id == activity.id
1190 end
1191 end
1192
1193 describe "fetch_follow_information_for_user" do
1194 test "syncronizes following/followers counters" do
1195 user =
1196 insert(:user,
1197 local: false,
1198 follower_address: "http://localhost:4001/users/fuser2/followers",
1199 following_address: "http://localhost:4001/users/fuser2/following"
1200 )
1201
1202 {:ok, info} = ActivityPub.fetch_follow_information_for_user(user)
1203 assert info.follower_count == 527
1204 assert info.following_count == 267
1205 end
1206
1207 test "detects hidden followers" do
1208 mock(fn env ->
1209 case env.url do
1210 "http://localhost:4001/users/masto_closed/followers?page=1" ->
1211 %Tesla.Env{status: 403, body: ""}
1212
1213 _ ->
1214 apply(HttpRequestMock, :request, [env])
1215 end
1216 end)
1217
1218 user =
1219 insert(:user,
1220 local: false,
1221 follower_address: "http://localhost:4001/users/masto_closed/followers",
1222 following_address: "http://localhost:4001/users/masto_closed/following"
1223 )
1224
1225 {:ok, info} = ActivityPub.fetch_follow_information_for_user(user)
1226 assert info.hide_followers == true
1227 assert info.hide_follows == false
1228 end
1229
1230 test "detects hidden follows" do
1231 mock(fn env ->
1232 case env.url do
1233 "http://localhost:4001/users/masto_closed/following?page=1" ->
1234 %Tesla.Env{status: 403, body: ""}
1235
1236 _ ->
1237 apply(HttpRequestMock, :request, [env])
1238 end
1239 end)
1240
1241 user =
1242 insert(:user,
1243 local: false,
1244 follower_address: "http://localhost:4001/users/masto_closed/followers",
1245 following_address: "http://localhost:4001/users/masto_closed/following"
1246 )
1247
1248 {:ok, info} = ActivityPub.fetch_follow_information_for_user(user)
1249 assert info.hide_followers == false
1250 assert info.hide_follows == true
1251 end
1252 end
1253 end