Merge branch 'fix/wrong-next-key-likes-json' into 'develop'
[akkoma] / test / web / activity_pub / activity_pub_controller_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
6 use Pleroma.Web.ConnCase
7 import Pleroma.Factory
8 alias Pleroma.Activity
9 alias Pleroma.Instances
10 alias Pleroma.Object
11 alias Pleroma.User
12 alias Pleroma.Web.ActivityPub.ObjectView
13 alias Pleroma.Web.ActivityPub.UserView
14 alias Pleroma.Web.ActivityPub.Utils
15 alias Pleroma.Web.CommonAPI
16
17 setup_all do
18 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
19
20 config_path = [:instance, :federating]
21 initial_setting = Pleroma.Config.get(config_path)
22
23 Pleroma.Config.put(config_path, true)
24 on_exit(fn -> Pleroma.Config.put(config_path, initial_setting) end)
25
26 :ok
27 end
28
29 describe "/relay" do
30 test "with the relay active, it returns the relay user", %{conn: conn} do
31 res =
32 conn
33 |> get(activity_pub_path(conn, :relay))
34 |> json_response(200)
35
36 assert res["id"] =~ "/relay"
37 end
38
39 test "with the relay disabled, it returns 404", %{conn: conn} do
40 Pleroma.Config.put([:instance, :allow_relay], false)
41
42 conn
43 |> get(activity_pub_path(conn, :relay))
44 |> json_response(404)
45 |> assert
46
47 Pleroma.Config.put([:instance, :allow_relay], true)
48 end
49 end
50
51 describe "/internal/fetch" do
52 test "it returns the internal fetch user", %{conn: conn} do
53 res =
54 conn
55 |> get(activity_pub_path(conn, :internal_fetch))
56 |> json_response(200)
57
58 assert res["id"] =~ "/fetch"
59 end
60 end
61
62 describe "/users/:nickname" do
63 test "it returns a json representation of the user with accept application/json", %{
64 conn: conn
65 } do
66 user = insert(:user)
67
68 conn =
69 conn
70 |> put_req_header("accept", "application/json")
71 |> get("/users/#{user.nickname}")
72
73 user = User.get_cached_by_id(user.id)
74
75 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
76 end
77
78 test "it returns a json representation of the user with accept application/activity+json", %{
79 conn: conn
80 } do
81 user = insert(:user)
82
83 conn =
84 conn
85 |> put_req_header("accept", "application/activity+json")
86 |> get("/users/#{user.nickname}")
87
88 user = User.get_cached_by_id(user.id)
89
90 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
91 end
92
93 test "it returns a json representation of the user with accept application/ld+json", %{
94 conn: conn
95 } do
96 user = insert(:user)
97
98 conn =
99 conn
100 |> put_req_header(
101 "accept",
102 "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
103 )
104 |> get("/users/#{user.nickname}")
105
106 user = User.get_cached_by_id(user.id)
107
108 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
109 end
110 end
111
112 describe "/object/:uuid" do
113 test "it returns a json representation of the object with accept application/json", %{
114 conn: conn
115 } do
116 note = insert(:note)
117 uuid = String.split(note.data["id"], "/") |> List.last()
118
119 conn =
120 conn
121 |> put_req_header("accept", "application/json")
122 |> get("/objects/#{uuid}")
123
124 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
125 end
126
127 test "it returns a json representation of the object with accept application/activity+json",
128 %{conn: conn} do
129 note = insert(:note)
130 uuid = String.split(note.data["id"], "/") |> List.last()
131
132 conn =
133 conn
134 |> put_req_header("accept", "application/activity+json")
135 |> get("/objects/#{uuid}")
136
137 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
138 end
139
140 test "it returns a json representation of the object with accept application/ld+json", %{
141 conn: conn
142 } do
143 note = insert(:note)
144 uuid = String.split(note.data["id"], "/") |> List.last()
145
146 conn =
147 conn
148 |> put_req_header(
149 "accept",
150 "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
151 )
152 |> get("/objects/#{uuid}")
153
154 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
155 end
156
157 test "it returns 404 for non-public messages", %{conn: conn} do
158 note = insert(:direct_note)
159 uuid = String.split(note.data["id"], "/") |> List.last()
160
161 conn =
162 conn
163 |> put_req_header("accept", "application/activity+json")
164 |> get("/objects/#{uuid}")
165
166 assert json_response(conn, 404)
167 end
168
169 test "it returns 404 for tombstone objects", %{conn: conn} do
170 tombstone = insert(:tombstone)
171 uuid = String.split(tombstone.data["id"], "/") |> List.last()
172
173 conn =
174 conn
175 |> put_req_header("accept", "application/activity+json")
176 |> get("/objects/#{uuid}")
177
178 assert json_response(conn, 404)
179 end
180 end
181
182 describe "/object/:uuid/likes" do
183 setup do
184 like = insert(:like_activity)
185 like_object_ap_id = Object.normalize(like).data["id"]
186
187 uuid =
188 like_object_ap_id
189 |> String.split("/")
190 |> List.last()
191
192 [id: like.data["id"], uuid: uuid]
193 end
194
195 test "it returns the like activities in a collection", %{conn: conn, id: id, uuid: uuid} do
196 result =
197 conn
198 |> put_req_header("accept", "application/activity+json")
199 |> get("/objects/#{uuid}/likes")
200 |> json_response(200)
201
202 assert List.first(result["first"]["orderedItems"])["id"] == id
203 assert result["type"] == "OrderedCollection"
204 assert result["totalItems"] == 1
205 refute result["first"]["next"]
206 end
207
208 test "it does not crash when page number is exceeded total pages", %{conn: conn, uuid: uuid} do
209 result =
210 conn
211 |> put_req_header("accept", "application/activity+json")
212 |> get("/objects/#{uuid}/likes?page=2")
213 |> json_response(200)
214
215 assert result["type"] == "OrderedCollectionPage"
216 assert result["totalItems"] == 1
217 refute result["next"]
218 assert Enum.empty?(result["orderedItems"])
219 end
220
221 test "it contains the next key when likes count is more than 10", %{conn: conn} do
222 note = insert(:note_activity)
223 insert_list(11, :like_activity, note_activity: note)
224
225 uuid =
226 note
227 |> Object.normalize()
228 |> Map.get(:data)
229 |> Map.get("id")
230 |> String.split("/")
231 |> List.last()
232
233 result =
234 conn
235 |> put_req_header("accept", "application/activity+json")
236 |> get("/objects/#{uuid}/likes?page=1")
237 |> json_response(200)
238
239 assert result["totalItems"] == 11
240 assert length(result["orderedItems"]) == 10
241 assert result["next"]
242 end
243 end
244
245 describe "/activities/:uuid" do
246 test "it returns a json representation of the activity", %{conn: conn} do
247 activity = insert(:note_activity)
248 uuid = String.split(activity.data["id"], "/") |> List.last()
249
250 conn =
251 conn
252 |> put_req_header("accept", "application/activity+json")
253 |> get("/activities/#{uuid}")
254
255 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: activity})
256 end
257
258 test "it returns 404 for non-public activities", %{conn: conn} do
259 activity = insert(:direct_note_activity)
260 uuid = String.split(activity.data["id"], "/") |> List.last()
261
262 conn =
263 conn
264 |> put_req_header("accept", "application/activity+json")
265 |> get("/activities/#{uuid}")
266
267 assert json_response(conn, 404)
268 end
269 end
270
271 describe "/inbox" do
272 test "it inserts an incoming activity into the database", %{conn: conn} do
273 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
274
275 conn =
276 conn
277 |> assign(:valid_signature, true)
278 |> put_req_header("content-type", "application/activity+json")
279 |> post("/inbox", data)
280
281 assert "ok" == json_response(conn, 200)
282 :timer.sleep(500)
283 assert Activity.get_by_ap_id(data["id"])
284 end
285
286 test "it clears `unreachable` federation status of the sender", %{conn: conn} do
287 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
288
289 sender_url = data["actor"]
290 Instances.set_consistently_unreachable(sender_url)
291 refute Instances.reachable?(sender_url)
292
293 conn =
294 conn
295 |> assign(:valid_signature, true)
296 |> put_req_header("content-type", "application/activity+json")
297 |> post("/inbox", data)
298
299 assert "ok" == json_response(conn, 200)
300 assert Instances.reachable?(sender_url)
301 end
302 end
303
304 describe "/users/:nickname/inbox" do
305 setup do
306 data =
307 File.read!("test/fixtures/mastodon-post-activity.json")
308 |> Poison.decode!()
309
310 [data: data]
311 end
312
313 test "it inserts an incoming activity into the database", %{conn: conn, data: data} do
314 user = insert(:user)
315 data = Map.put(data, "bcc", [user.ap_id])
316
317 conn =
318 conn
319 |> assign(:valid_signature, true)
320 |> put_req_header("content-type", "application/activity+json")
321 |> post("/users/#{user.nickname}/inbox", data)
322
323 assert "ok" == json_response(conn, 200)
324 :timer.sleep(500)
325 assert Activity.get_by_ap_id(data["id"])
326 end
327
328 test "it accepts messages from actors that are followed by the user", %{
329 conn: conn,
330 data: data
331 } do
332 recipient = insert(:user)
333 actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})
334
335 {:ok, recipient} = User.follow(recipient, actor)
336
337 object =
338 data["object"]
339 |> Map.put("attributedTo", actor.ap_id)
340
341 data =
342 data
343 |> Map.put("actor", actor.ap_id)
344 |> Map.put("object", object)
345
346 conn =
347 conn
348 |> assign(:valid_signature, true)
349 |> put_req_header("content-type", "application/activity+json")
350 |> post("/users/#{recipient.nickname}/inbox", data)
351
352 assert "ok" == json_response(conn, 200)
353 :timer.sleep(500)
354 assert Activity.get_by_ap_id(data["id"])
355 end
356
357 test "it rejects reads from other users", %{conn: conn} do
358 user = insert(:user)
359 otheruser = insert(:user)
360
361 conn =
362 conn
363 |> assign(:user, otheruser)
364 |> put_req_header("accept", "application/activity+json")
365 |> get("/users/#{user.nickname}/inbox")
366
367 assert json_response(conn, 403)
368 end
369
370 test "it returns a note activity in a collection", %{conn: conn} do
371 note_activity = insert(:direct_note_activity)
372 note_object = Object.normalize(note_activity)
373 user = User.get_cached_by_ap_id(hd(note_activity.data["to"]))
374
375 conn =
376 conn
377 |> assign(:user, user)
378 |> put_req_header("accept", "application/activity+json")
379 |> get("/users/#{user.nickname}/inbox")
380
381 assert response(conn, 200) =~ note_object.data["content"]
382 end
383
384 test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
385 user = insert(:user)
386 data = Map.put(data, "bcc", [user.ap_id])
387
388 sender_host = URI.parse(data["actor"]).host
389 Instances.set_consistently_unreachable(sender_host)
390 refute Instances.reachable?(sender_host)
391
392 conn =
393 conn
394 |> assign(:valid_signature, true)
395 |> put_req_header("content-type", "application/activity+json")
396 |> post("/users/#{user.nickname}/inbox", data)
397
398 assert "ok" == json_response(conn, 200)
399 assert Instances.reachable?(sender_host)
400 end
401
402 test "it removes all follower collections but actor's", %{conn: conn} do
403 [actor, recipient] = insert_pair(:user)
404
405 data =
406 File.read!("test/fixtures/activitypub-client-post-activity.json")
407 |> Poison.decode!()
408
409 object = Map.put(data["object"], "attributedTo", actor.ap_id)
410
411 data =
412 data
413 |> Map.put("id", Utils.generate_object_id())
414 |> Map.put("actor", actor.ap_id)
415 |> Map.put("object", object)
416 |> Map.put("cc", [
417 recipient.follower_address,
418 actor.follower_address
419 ])
420 |> Map.put("to", [
421 recipient.ap_id,
422 recipient.follower_address,
423 "https://www.w3.org/ns/activitystreams#Public"
424 ])
425
426 conn
427 |> assign(:valid_signature, true)
428 |> put_req_header("content-type", "application/activity+json")
429 |> post("/users/#{recipient.nickname}/inbox", data)
430 |> json_response(200)
431
432 activity = Activity.get_by_ap_id(data["id"])
433
434 assert activity.id
435 assert actor.follower_address in activity.recipients
436 assert actor.follower_address in activity.data["cc"]
437
438 refute recipient.follower_address in activity.recipients
439 refute recipient.follower_address in activity.data["cc"]
440 refute recipient.follower_address in activity.data["to"]
441 end
442 end
443
444 describe "/users/:nickname/outbox" do
445 test "it will not bomb when there is no activity", %{conn: conn} do
446 user = insert(:user)
447
448 conn =
449 conn
450 |> put_req_header("accept", "application/activity+json")
451 |> get("/users/#{user.nickname}/outbox")
452
453 result = json_response(conn, 200)
454 assert user.ap_id <> "/outbox" == result["id"]
455 end
456
457 test "it returns a note activity in a collection", %{conn: conn} do
458 note_activity = insert(:note_activity)
459 note_object = Object.normalize(note_activity)
460 user = User.get_cached_by_ap_id(note_activity.data["actor"])
461
462 conn =
463 conn
464 |> put_req_header("accept", "application/activity+json")
465 |> get("/users/#{user.nickname}/outbox")
466
467 assert response(conn, 200) =~ note_object.data["content"]
468 end
469
470 test "it returns an announce activity in a collection", %{conn: conn} do
471 announce_activity = insert(:announce_activity)
472 user = User.get_cached_by_ap_id(announce_activity.data["actor"])
473
474 conn =
475 conn
476 |> put_req_header("accept", "application/activity+json")
477 |> get("/users/#{user.nickname}/outbox")
478
479 assert response(conn, 200) =~ announce_activity.data["object"]
480 end
481
482 test "it rejects posts from other users", %{conn: conn} do
483 data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!()
484 user = insert(:user)
485 otheruser = insert(:user)
486
487 conn =
488 conn
489 |> assign(:user, otheruser)
490 |> put_req_header("content-type", "application/activity+json")
491 |> post("/users/#{user.nickname}/outbox", data)
492
493 assert json_response(conn, 403)
494 end
495
496 test "it inserts an incoming create activity into the database", %{conn: conn} do
497 data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!()
498 user = insert(:user)
499
500 conn =
501 conn
502 |> assign(:user, user)
503 |> put_req_header("content-type", "application/activity+json")
504 |> post("/users/#{user.nickname}/outbox", data)
505
506 result = json_response(conn, 201)
507 assert Activity.get_by_ap_id(result["id"])
508 end
509
510 test "it rejects an incoming activity with bogus type", %{conn: conn} do
511 data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!()
512 user = insert(:user)
513
514 data =
515 data
516 |> Map.put("type", "BadType")
517
518 conn =
519 conn
520 |> assign(:user, user)
521 |> put_req_header("content-type", "application/activity+json")
522 |> post("/users/#{user.nickname}/outbox", data)
523
524 assert json_response(conn, 400)
525 end
526
527 test "it erects a tombstone when receiving a delete activity", %{conn: conn} do
528 note_activity = insert(:note_activity)
529 note_object = Object.normalize(note_activity)
530 user = User.get_cached_by_ap_id(note_activity.data["actor"])
531
532 data = %{
533 type: "Delete",
534 object: %{
535 id: note_object.data["id"]
536 }
537 }
538
539 conn =
540 conn
541 |> assign(:user, user)
542 |> put_req_header("content-type", "application/activity+json")
543 |> post("/users/#{user.nickname}/outbox", data)
544
545 result = json_response(conn, 201)
546 assert Activity.get_by_ap_id(result["id"])
547
548 assert object = Object.get_by_ap_id(note_object.data["id"])
549 assert object.data["type"] == "Tombstone"
550 end
551
552 test "it rejects delete activity of object from other actor", %{conn: conn} do
553 note_activity = insert(:note_activity)
554 note_object = Object.normalize(note_activity)
555 user = insert(:user)
556
557 data = %{
558 type: "Delete",
559 object: %{
560 id: note_object.data["id"]
561 }
562 }
563
564 conn =
565 conn
566 |> assign(:user, user)
567 |> put_req_header("content-type", "application/activity+json")
568 |> post("/users/#{user.nickname}/outbox", data)
569
570 assert json_response(conn, 400)
571 end
572
573 test "it increases like count when receiving a like action", %{conn: conn} do
574 note_activity = insert(:note_activity)
575 note_object = Object.normalize(note_activity)
576 user = User.get_cached_by_ap_id(note_activity.data["actor"])
577
578 data = %{
579 type: "Like",
580 object: %{
581 id: note_object.data["id"]
582 }
583 }
584
585 conn =
586 conn
587 |> assign(:user, user)
588 |> put_req_header("content-type", "application/activity+json")
589 |> post("/users/#{user.nickname}/outbox", data)
590
591 result = json_response(conn, 201)
592 assert Activity.get_by_ap_id(result["id"])
593
594 assert object = Object.get_by_ap_id(note_object.data["id"])
595 assert object.data["like_count"] == 1
596 end
597 end
598
599 describe "/users/:nickname/followers" do
600 test "it returns the followers in a collection", %{conn: conn} do
601 user = insert(:user)
602 user_two = insert(:user)
603 User.follow(user, user_two)
604
605 result =
606 conn
607 |> get("/users/#{user_two.nickname}/followers")
608 |> json_response(200)
609
610 assert result["first"]["orderedItems"] == [user.ap_id]
611 end
612
613 test "it returns returns a uri if the user has 'hide_followers' set", %{conn: conn} do
614 user = insert(:user)
615 user_two = insert(:user, %{info: %{hide_followers: true}})
616 User.follow(user, user_two)
617
618 result =
619 conn
620 |> get("/users/#{user_two.nickname}/followers")
621 |> json_response(200)
622
623 assert is_binary(result["first"])
624 end
625
626 test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is not authenticated",
627 %{conn: conn} do
628 user = insert(:user, %{info: %{hide_followers: true}})
629
630 result =
631 conn
632 |> get("/users/#{user.nickname}/followers?page=1")
633
634 assert result.status == 403
635 assert result.resp_body == ""
636 end
637
638 test "it renders the page, if the user has 'hide_followers' set and the request is authenticated with the same user",
639 %{conn: conn} do
640 user = insert(:user, %{info: %{hide_followers: true}})
641 other_user = insert(:user)
642 {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
643
644 result =
645 conn
646 |> assign(:user, user)
647 |> get("/users/#{user.nickname}/followers?page=1")
648 |> json_response(200)
649
650 assert result["totalItems"] == 1
651 assert result["orderedItems"] == [other_user.ap_id]
652 end
653
654 test "it works for more than 10 users", %{conn: conn} do
655 user = insert(:user)
656
657 Enum.each(1..15, fn _ ->
658 other_user = insert(:user)
659 User.follow(other_user, user)
660 end)
661
662 result =
663 conn
664 |> get("/users/#{user.nickname}/followers")
665 |> json_response(200)
666
667 assert length(result["first"]["orderedItems"]) == 10
668 assert result["first"]["totalItems"] == 15
669 assert result["totalItems"] == 15
670
671 result =
672 conn
673 |> get("/users/#{user.nickname}/followers?page=2")
674 |> json_response(200)
675
676 assert length(result["orderedItems"]) == 5
677 assert result["totalItems"] == 15
678 end
679 end
680
681 describe "/users/:nickname/following" do
682 test "it returns the following in a collection", %{conn: conn} do
683 user = insert(:user)
684 user_two = insert(:user)
685 User.follow(user, user_two)
686
687 result =
688 conn
689 |> get("/users/#{user.nickname}/following")
690 |> json_response(200)
691
692 assert result["first"]["orderedItems"] == [user_two.ap_id]
693 end
694
695 test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do
696 user = insert(:user, %{info: %{hide_follows: true}})
697 user_two = insert(:user)
698 User.follow(user, user_two)
699
700 result =
701 conn
702 |> get("/users/#{user.nickname}/following")
703 |> json_response(200)
704
705 assert is_binary(result["first"])
706 end
707
708 test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is not authenticated",
709 %{conn: conn} do
710 user = insert(:user, %{info: %{hide_follows: true}})
711
712 result =
713 conn
714 |> get("/users/#{user.nickname}/following?page=1")
715
716 assert result.status == 403
717 assert result.resp_body == ""
718 end
719
720 test "it renders the page, if the user has 'hide_follows' set and the request is authenticated with the same user",
721 %{conn: conn} do
722 user = insert(:user, %{info: %{hide_follows: true}})
723 other_user = insert(:user)
724 {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
725
726 result =
727 conn
728 |> assign(:user, user)
729 |> get("/users/#{user.nickname}/following?page=1")
730 |> json_response(200)
731
732 assert result["totalItems"] == 1
733 assert result["orderedItems"] == [other_user.ap_id]
734 end
735
736 test "it works for more than 10 users", %{conn: conn} do
737 user = insert(:user)
738
739 Enum.each(1..15, fn _ ->
740 user = User.get_cached_by_id(user.id)
741 other_user = insert(:user)
742 User.follow(user, other_user)
743 end)
744
745 result =
746 conn
747 |> get("/users/#{user.nickname}/following")
748 |> json_response(200)
749
750 assert length(result["first"]["orderedItems"]) == 10
751 assert result["first"]["totalItems"] == 15
752 assert result["totalItems"] == 15
753
754 result =
755 conn
756 |> get("/users/#{user.nickname}/following?page=2")
757 |> json_response(200)
758
759 assert length(result["orderedItems"]) == 5
760 assert result["totalItems"] == 15
761 end
762 end
763 end