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