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