[#1149] Merge remote-tracking branch 'remotes/upstream/develop' into 1149-oban-job...
[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 describe "streaming out participations" do
25 test "it streams them out" do
26 user = insert(:user)
27 {:ok, activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"})
28
29 {:ok, conversation} = Pleroma.Conversation.create_or_bump_for(activity)
30
31 participations =
32 conversation.participations
33 |> Repo.preload(:user)
34
35 with_mock Pleroma.Web.Streamer,
36 stream: fn _, _ -> nil end do
37 ActivityPub.stream_out_participations(conversation.participations)
38
39 Enum.each(participations, fn participation ->
40 assert called(Pleroma.Web.Streamer.stream("participation", participation))
41 end)
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 "doesn't return reblogs for users for whom reblogs have been muted" do
651 activity = insert(:note_activity)
652 user = insert(:user)
653 booster = insert(:user)
654 {:ok, user} = CommonAPI.hide_reblogs(user, booster)
655
656 {:ok, activity, _} = CommonAPI.repeat(activity.id, booster)
657
658 activities = ActivityPub.fetch_activities([], %{"muting_user" => user})
659
660 refute Enum.any?(activities, fn %{id: id} -> id == activity.id end)
661 end
662
663 test "returns reblogs for users for whom reblogs have not been muted" do
664 activity = insert(:note_activity)
665 user = insert(:user)
666 booster = insert(:user)
667 {:ok, user} = CommonAPI.hide_reblogs(user, booster)
668 {:ok, user} = CommonAPI.show_reblogs(user, booster)
669
670 {:ok, activity, _} = CommonAPI.repeat(activity.id, booster)
671
672 activities = ActivityPub.fetch_activities([], %{"muting_user" => user})
673
674 assert Enum.any?(activities, fn %{id: id} -> id == activity.id end)
675 end
676 end
677
678 describe "like an object" do
679 test "adds a like activity to the db" do
680 note_activity = insert(:note_activity)
681 assert object = Object.normalize(note_activity)
682
683 user = insert(:user)
684 user_two = insert(:user)
685
686 {:ok, like_activity, object} = ActivityPub.like(user, object)
687
688 assert like_activity.data["actor"] == user.ap_id
689 assert like_activity.data["type"] == "Like"
690 assert like_activity.data["object"] == object.data["id"]
691 assert like_activity.data["to"] == [User.ap_followers(user), note_activity.data["actor"]]
692 assert like_activity.data["context"] == object.data["context"]
693 assert object.data["like_count"] == 1
694 assert object.data["likes"] == [user.ap_id]
695
696 # Just return the original activity if the user already liked it.
697 {:ok, same_like_activity, object} = ActivityPub.like(user, object)
698
699 assert like_activity == same_like_activity
700 assert object.data["likes"] == [user.ap_id]
701 assert object.data["like_count"] == 1
702
703 {:ok, _like_activity, object} = ActivityPub.like(user_two, object)
704 assert object.data["like_count"] == 2
705 end
706 end
707
708 describe "unliking" do
709 test "unliking a previously liked object" do
710 note_activity = insert(:note_activity)
711 object = Object.normalize(note_activity)
712 user = insert(:user)
713
714 # Unliking something that hasn't been liked does nothing
715 {:ok, object} = ActivityPub.unlike(user, object)
716 assert object.data["like_count"] == 0
717
718 {:ok, like_activity, object} = ActivityPub.like(user, object)
719 assert object.data["like_count"] == 1
720
721 {:ok, _, _, object} = ActivityPub.unlike(user, object)
722 assert object.data["like_count"] == 0
723
724 assert Activity.get_by_id(like_activity.id) == nil
725 end
726 end
727
728 describe "announcing an object" do
729 test "adds an announce activity to the db" do
730 note_activity = insert(:note_activity)
731 object = Object.normalize(note_activity)
732 user = insert(:user)
733
734 {:ok, announce_activity, object} = ActivityPub.announce(user, object)
735 assert object.data["announcement_count"] == 1
736 assert object.data["announcements"] == [user.ap_id]
737
738 assert announce_activity.data["to"] == [
739 User.ap_followers(user),
740 note_activity.data["actor"]
741 ]
742
743 assert announce_activity.data["object"] == object.data["id"]
744 assert announce_activity.data["actor"] == user.ap_id
745 assert announce_activity.data["context"] == object.data["context"]
746 end
747 end
748
749 describe "unannouncing an object" do
750 test "unannouncing a previously announced object" do
751 note_activity = insert(:note_activity)
752 object = Object.normalize(note_activity)
753 user = insert(:user)
754
755 # Unannouncing an object that is not announced does nothing
756 # {:ok, object} = ActivityPub.unannounce(user, object)
757 # assert object.data["announcement_count"] == 0
758
759 {:ok, announce_activity, object} = ActivityPub.announce(user, object)
760 assert object.data["announcement_count"] == 1
761
762 {:ok, unannounce_activity, object} = ActivityPub.unannounce(user, object)
763 assert object.data["announcement_count"] == 0
764
765 assert unannounce_activity.data["to"] == [
766 User.ap_followers(user),
767 announce_activity.data["actor"]
768 ]
769
770 assert unannounce_activity.data["type"] == "Undo"
771 assert unannounce_activity.data["object"] == announce_activity.data
772 assert unannounce_activity.data["actor"] == user.ap_id
773 assert unannounce_activity.data["context"] == announce_activity.data["context"]
774
775 assert Activity.get_by_id(announce_activity.id) == nil
776 end
777 end
778
779 describe "uploading files" do
780 test "copies the file to the configured folder" do
781 file = %Plug.Upload{
782 content_type: "image/jpg",
783 path: Path.absname("test/fixtures/image.jpg"),
784 filename: "an_image.jpg"
785 }
786
787 {:ok, %Object{} = object} = ActivityPub.upload(file)
788 assert object.data["name"] == "an_image.jpg"
789 end
790
791 test "works with base64 encoded images" do
792 file = %{
793 "img" => data_uri()
794 }
795
796 {:ok, %Object{}} = ActivityPub.upload(file)
797 end
798 end
799
800 describe "fetch the latest Follow" do
801 test "fetches the latest Follow activity" do
802 %Activity{data: %{"type" => "Follow"}} = activity = insert(:follow_activity)
803 follower = Repo.get_by(User, ap_id: activity.data["actor"])
804 followed = Repo.get_by(User, ap_id: activity.data["object"])
805
806 assert activity == Utils.fetch_latest_follow(follower, followed)
807 end
808 end
809
810 describe "following / unfollowing" do
811 test "creates a follow activity" do
812 follower = insert(:user)
813 followed = insert(:user)
814
815 {:ok, activity} = ActivityPub.follow(follower, followed)
816 assert activity.data["type"] == "Follow"
817 assert activity.data["actor"] == follower.ap_id
818 assert activity.data["object"] == followed.ap_id
819 end
820
821 test "creates an undo activity for the last follow" do
822 follower = insert(:user)
823 followed = insert(:user)
824
825 {:ok, follow_activity} = ActivityPub.follow(follower, followed)
826 {:ok, activity} = ActivityPub.unfollow(follower, followed)
827
828 assert activity.data["type"] == "Undo"
829 assert activity.data["actor"] == follower.ap_id
830
831 embedded_object = activity.data["object"]
832 assert is_map(embedded_object)
833 assert embedded_object["type"] == "Follow"
834 assert embedded_object["object"] == followed.ap_id
835 assert embedded_object["id"] == follow_activity.data["id"]
836 end
837 end
838
839 describe "blocking / unblocking" do
840 test "creates a block activity" do
841 blocker = insert(:user)
842 blocked = insert(:user)
843
844 {:ok, activity} = ActivityPub.block(blocker, blocked)
845
846 assert activity.data["type"] == "Block"
847 assert activity.data["actor"] == blocker.ap_id
848 assert activity.data["object"] == blocked.ap_id
849 end
850
851 test "creates an undo activity for the last block" do
852 blocker = insert(:user)
853 blocked = insert(:user)
854
855 {:ok, block_activity} = ActivityPub.block(blocker, blocked)
856 {:ok, activity} = ActivityPub.unblock(blocker, blocked)
857
858 assert activity.data["type"] == "Undo"
859 assert activity.data["actor"] == blocker.ap_id
860
861 embedded_object = activity.data["object"]
862 assert is_map(embedded_object)
863 assert embedded_object["type"] == "Block"
864 assert embedded_object["object"] == blocked.ap_id
865 assert embedded_object["id"] == block_activity.data["id"]
866 end
867 end
868
869 describe "deletion" do
870 test "it creates a delete activity and deletes the original object" do
871 note = insert(:note_activity)
872 object = Object.normalize(note)
873 {:ok, delete} = ActivityPub.delete(object)
874
875 assert delete.data["type"] == "Delete"
876 assert delete.data["actor"] == note.data["actor"]
877 assert delete.data["object"] == object.data["id"]
878
879 assert Activity.get_by_id(delete.id) != nil
880
881 assert Repo.get(Object, object.id).data["type"] == "Tombstone"
882 end
883
884 test "decrements user note count only for public activities" do
885 user = insert(:user, info: %{note_count: 10})
886
887 {:ok, a1} =
888 CommonAPI.post(User.get_cached_by_id(user.id), %{
889 "status" => "yeah",
890 "visibility" => "public"
891 })
892
893 {:ok, a2} =
894 CommonAPI.post(User.get_cached_by_id(user.id), %{
895 "status" => "yeah",
896 "visibility" => "unlisted"
897 })
898
899 {:ok, a3} =
900 CommonAPI.post(User.get_cached_by_id(user.id), %{
901 "status" => "yeah",
902 "visibility" => "private"
903 })
904
905 {:ok, a4} =
906 CommonAPI.post(User.get_cached_by_id(user.id), %{
907 "status" => "yeah",
908 "visibility" => "direct"
909 })
910
911 {:ok, _} = Object.normalize(a1) |> ActivityPub.delete()
912 {:ok, _} = Object.normalize(a2) |> ActivityPub.delete()
913 {:ok, _} = Object.normalize(a3) |> ActivityPub.delete()
914 {:ok, _} = Object.normalize(a4) |> ActivityPub.delete()
915
916 user = User.get_cached_by_id(user.id)
917 assert user.info.note_count == 10
918 end
919
920 test "it creates a delete activity and checks that it is also sent to users mentioned by the deleted object" do
921 user = insert(:user)
922 note = insert(:note_activity)
923 object = Object.normalize(note)
924
925 {:ok, object} =
926 object
927 |> Object.change(%{
928 data: %{
929 "actor" => object.data["actor"],
930 "id" => object.data["id"],
931 "to" => [user.ap_id],
932 "type" => "Note"
933 }
934 })
935 |> Object.update_and_set_cache()
936
937 {:ok, delete} = ActivityPub.delete(object)
938
939 assert user.ap_id in delete.data["to"]
940 end
941
942 test "decreases reply count" do
943 user = insert(:user)
944 user2 = insert(:user)
945
946 {:ok, activity} = CommonAPI.post(user, %{"status" => "1", "visibility" => "public"})
947 reply_data = %{"status" => "1", "in_reply_to_status_id" => activity.id}
948 ap_id = activity.data["id"]
949
950 {:ok, public_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "public"))
951 {:ok, unlisted_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "unlisted"))
952 {:ok, private_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "private"))
953 {:ok, direct_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "direct"))
954
955 _ = CommonAPI.delete(direct_reply.id, user2)
956 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
957 assert object.data["repliesCount"] == 2
958
959 _ = CommonAPI.delete(private_reply.id, user2)
960 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
961 assert object.data["repliesCount"] == 2
962
963 _ = CommonAPI.delete(public_reply.id, user2)
964 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
965 assert object.data["repliesCount"] == 1
966
967 _ = CommonAPI.delete(unlisted_reply.id, user2)
968 assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
969 assert object.data["repliesCount"] == 0
970 end
971 end
972
973 describe "timeline post-processing" do
974 test "it filters broken threads" do
975 user1 = insert(:user)
976 user2 = insert(:user)
977 user3 = insert(:user)
978
979 {:ok, user1} = User.follow(user1, user3)
980 assert User.following?(user1, user3)
981
982 {:ok, user2} = User.follow(user2, user3)
983 assert User.following?(user2, user3)
984
985 {:ok, user3} = User.follow(user3, user2)
986 assert User.following?(user3, user2)
987
988 {:ok, public_activity} = CommonAPI.post(user3, %{"status" => "hi 1"})
989
990 {:ok, private_activity_1} =
991 CommonAPI.post(user3, %{"status" => "hi 2", "visibility" => "private"})
992
993 {:ok, private_activity_2} =
994 CommonAPI.post(user2, %{
995 "status" => "hi 3",
996 "visibility" => "private",
997 "in_reply_to_status_id" => private_activity_1.id
998 })
999
1000 {:ok, private_activity_3} =
1001 CommonAPI.post(user3, %{
1002 "status" => "hi 4",
1003 "visibility" => "private",
1004 "in_reply_to_status_id" => private_activity_2.id
1005 })
1006
1007 activities =
1008 ActivityPub.fetch_activities([user1.ap_id | user1.following])
1009 |> Enum.map(fn a -> a.id end)
1010
1011 private_activity_1 = Activity.get_by_ap_id_with_object(private_activity_1.data["id"])
1012
1013 assert [public_activity.id, private_activity_1.id, private_activity_3.id] == activities
1014
1015 assert length(activities) == 3
1016
1017 activities =
1018 ActivityPub.fetch_activities([user1.ap_id | user1.following], %{"user" => user1})
1019 |> Enum.map(fn a -> a.id end)
1020
1021 assert [public_activity.id, private_activity_1.id] == activities
1022 assert length(activities) == 2
1023 end
1024 end
1025
1026 describe "update" do
1027 test "it creates an update activity with the new user data" do
1028 user = insert(:user)
1029 {:ok, user} = User.ensure_keys_present(user)
1030 user_data = Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user})
1031
1032 {:ok, update} =
1033 ActivityPub.update(%{
1034 actor: user_data["id"],
1035 to: [user.follower_address],
1036 cc: [],
1037 object: user_data
1038 })
1039
1040 assert update.data["actor"] == user.ap_id
1041 assert update.data["to"] == [user.follower_address]
1042 assert embedded_object = update.data["object"]
1043 assert embedded_object["id"] == user_data["id"]
1044 assert embedded_object["type"] == user_data["type"]
1045 end
1046 end
1047
1048 test "returned pinned statuses" do
1049 Pleroma.Config.put([:instance, :max_pinned_statuses], 3)
1050 user = insert(:user)
1051
1052 {:ok, activity_one} = CommonAPI.post(user, %{"status" => "HI!!!"})
1053 {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"})
1054 {:ok, activity_three} = CommonAPI.post(user, %{"status" => "HI!!!"})
1055
1056 CommonAPI.pin(activity_one.id, user)
1057 user = refresh_record(user)
1058
1059 CommonAPI.pin(activity_two.id, user)
1060 user = refresh_record(user)
1061
1062 CommonAPI.pin(activity_three.id, user)
1063 user = refresh_record(user)
1064
1065 activities = ActivityPub.fetch_user_activities(user, nil, %{"pinned" => "true"})
1066
1067 assert 3 = length(activities)
1068 end
1069
1070 test "it can create a Flag activity" do
1071 reporter = insert(:user)
1072 target_account = insert(:user)
1073 {:ok, activity} = CommonAPI.post(target_account, %{"status" => "foobar"})
1074 context = Utils.generate_context_id()
1075 content = "foobar"
1076
1077 reporter_ap_id = reporter.ap_id
1078 target_ap_id = target_account.ap_id
1079 activity_ap_id = activity.data["id"]
1080
1081 assert {:ok, activity} =
1082 ActivityPub.flag(%{
1083 actor: reporter,
1084 context: context,
1085 account: target_account,
1086 statuses: [activity],
1087 content: content
1088 })
1089
1090 assert %Activity{
1091 actor: ^reporter_ap_id,
1092 data: %{
1093 "type" => "Flag",
1094 "content" => ^content,
1095 "context" => ^context,
1096 "object" => [^target_ap_id, ^activity_ap_id]
1097 }
1098 } = activity
1099 end
1100
1101 test "fetch_activities/2 returns activities addressed to a list " do
1102 user = insert(:user)
1103 member = insert(:user)
1104 {:ok, list} = Pleroma.List.create("foo", user)
1105 {:ok, list} = Pleroma.List.follow(list, member)
1106
1107 {:ok, activity} =
1108 CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"})
1109
1110 activity = Repo.preload(activity, :bookmark)
1111 activity = %Activity{activity | thread_muted?: !!activity.thread_muted?}
1112
1113 assert ActivityPub.fetch_activities([], %{"user" => user}) == [activity]
1114 end
1115
1116 def data_uri do
1117 File.read!("test/fixtures/avatar_data_uri")
1118 end
1119
1120 describe "fetch_activities_bounded" do
1121 test "fetches private posts for followed users" do
1122 user = insert(:user)
1123
1124 {:ok, activity} =
1125 CommonAPI.post(user, %{
1126 "status" => "thought I looked cute might delete later :3",
1127 "visibility" => "private"
1128 })
1129
1130 [result] = ActivityPub.fetch_activities_bounded([user.follower_address], [])
1131 assert result.id == activity.id
1132 end
1133
1134 test "fetches only public posts for other users" do
1135 user = insert(:user)
1136 {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe", "visibility" => "public"})
1137
1138 {:ok, _private_activity} =
1139 CommonAPI.post(user, %{
1140 "status" => "why is tenshi eating a corndog so cute?",
1141 "visibility" => "private"
1142 })
1143
1144 [result] = ActivityPub.fetch_activities_bounded([], [user.follower_address])
1145 assert result.id == activity.id
1146 end
1147 end
1148
1149 describe "fetch_follow_information_for_user" do
1150 test "syncronizes following/followers counters" do
1151 user =
1152 insert(:user,
1153 local: false,
1154 follower_address: "http://localhost:4001/users/fuser2/followers",
1155 following_address: "http://localhost:4001/users/fuser2/following"
1156 )
1157
1158 {:ok, info} = ActivityPub.fetch_follow_information_for_user(user)
1159 assert info.follower_count == 527
1160 assert info.following_count == 267
1161 end
1162
1163 test "detects hidden followers" do
1164 mock(fn env ->
1165 case env.url do
1166 "http://localhost:4001/users/masto_closed/followers?page=1" ->
1167 %Tesla.Env{status: 403, body: ""}
1168
1169 _ ->
1170 apply(HttpRequestMock, :request, [env])
1171 end
1172 end)
1173
1174 user =
1175 insert(:user,
1176 local: false,
1177 follower_address: "http://localhost:4001/users/masto_closed/followers",
1178 following_address: "http://localhost:4001/users/masto_closed/following"
1179 )
1180
1181 {:ok, info} = ActivityPub.fetch_follow_information_for_user(user)
1182 assert info.hide_followers == true
1183 assert info.hide_follows == false
1184 end
1185
1186 test "detects hidden follows" do
1187 mock(fn env ->
1188 case env.url do
1189 "http://localhost:4001/users/masto_closed/following?page=1" ->
1190 %Tesla.Env{status: 403, body: ""}
1191
1192 _ ->
1193 apply(HttpRequestMock, :request, [env])
1194 end
1195 end)
1196
1197 user =
1198 insert(:user,
1199 local: false,
1200 follower_address: "http://localhost:4001/users/masto_closed/followers",
1201 following_address: "http://localhost:4001/users/masto_closed/following"
1202 )
1203
1204 {:ok, info} = ActivityPub.fetch_follow_information_for_user(user)
1205 assert info.hide_followers == false
1206 assert info.hide_follows == true
1207 end
1208 end
1209 end