1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
6 use Pleroma.Web.ConnCase
9 alias Pleroma.Instances
12 alias Pleroma.Web.ActivityPub.ObjectView
13 alias Pleroma.Web.ActivityPub.UserView
14 alias Pleroma.Web.ActivityPub.Utils
15 alias Pleroma.Web.CommonAPI
18 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
20 config_path = [:instance, :federating]
21 initial_setting = Pleroma.Config.get(config_path)
23 Pleroma.Config.put(config_path, true)
24 on_exit(fn -> Pleroma.Config.put(config_path, initial_setting) end)
30 test "with the relay active, it returns the relay user", %{conn: conn} do
33 |> get(activity_pub_path(conn, :relay))
36 assert res["id"] =~ "/relay"
39 test "with the relay disabled, it returns 404", %{conn: conn} do
40 Pleroma.Config.put([:instance, :allow_relay], false)
43 |> get(activity_pub_path(conn, :relay))
47 Pleroma.Config.put([:instance, :allow_relay], true)
51 describe "/users/:nickname" do
52 test "it returns a json representation of the user with accept application/json", %{
59 |> put_req_header("accept", "application/json")
60 |> get("/users/#{user.nickname}")
62 user = User.get_cached_by_id(user.id)
64 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
67 test "it returns a json representation of the user with accept application/activity+json", %{
74 |> put_req_header("accept", "application/activity+json")
75 |> get("/users/#{user.nickname}")
77 user = User.get_cached_by_id(user.id)
79 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
82 test "it returns a json representation of the user with accept application/ld+json", %{
91 "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
93 |> get("/users/#{user.nickname}")
95 user = User.get_cached_by_id(user.id)
97 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
101 describe "/object/:uuid" do
102 test "it returns a json representation of the object with accept application/json", %{
106 uuid = String.split(note.data["id"], "/") |> List.last()
110 |> put_req_header("accept", "application/json")
111 |> get("/objects/#{uuid}")
113 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
116 test "it returns a json representation of the object with accept application/activity+json",
119 uuid = String.split(note.data["id"], "/") |> List.last()
123 |> put_req_header("accept", "application/activity+json")
124 |> get("/objects/#{uuid}")
126 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
129 test "it returns a json representation of the object with accept application/ld+json", %{
133 uuid = String.split(note.data["id"], "/") |> List.last()
139 "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
141 |> get("/objects/#{uuid}")
143 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
146 test "it returns 404 for non-public messages", %{conn: conn} do
147 note = insert(:direct_note)
148 uuid = String.split(note.data["id"], "/") |> List.last()
152 |> put_req_header("accept", "application/activity+json")
153 |> get("/objects/#{uuid}")
155 assert json_response(conn, 404)
158 test "it returns 404 for tombstone objects", %{conn: conn} do
159 tombstone = insert(:tombstone)
160 uuid = String.split(tombstone.data["id"], "/") |> List.last()
164 |> put_req_header("accept", "application/activity+json")
165 |> get("/objects/#{uuid}")
167 assert json_response(conn, 404)
171 describe "/object/:uuid/likes" do
172 test "it returns the like activities in a collection", %{conn: conn} do
173 like = insert(:like_activity)
174 like_object_ap_id = Object.normalize(like).data["id"]
175 uuid = String.split(like_object_ap_id, "/") |> List.last()
179 |> put_req_header("accept", "application/activity+json")
180 |> get("/objects/#{uuid}/likes")
181 |> json_response(200)
183 assert List.first(result["first"]["orderedItems"])["id"] == like.data["id"]
187 describe "/activities/:uuid" do
188 test "it returns a json representation of the activity", %{conn: conn} do
189 activity = insert(:note_activity)
190 uuid = String.split(activity.data["id"], "/") |> List.last()
194 |> put_req_header("accept", "application/activity+json")
195 |> get("/activities/#{uuid}")
197 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: activity})
200 test "it returns 404 for non-public activities", %{conn: conn} do
201 activity = insert(:direct_note_activity)
202 uuid = String.split(activity.data["id"], "/") |> List.last()
206 |> put_req_header("accept", "application/activity+json")
207 |> get("/activities/#{uuid}")
209 assert json_response(conn, 404)
214 test "it inserts an incoming activity into the database", %{conn: conn} do
215 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
219 |> assign(:valid_signature, true)
220 |> put_req_header("content-type", "application/activity+json")
221 |> post("/inbox", data)
223 assert "ok" == json_response(conn, 200)
225 assert Activity.get_by_ap_id(data["id"])
228 test "it clears `unreachable` federation status of the sender", %{conn: conn} do
229 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
231 sender_url = data["actor"]
232 Instances.set_consistently_unreachable(sender_url)
233 refute Instances.reachable?(sender_url)
237 |> assign(:valid_signature, true)
238 |> put_req_header("content-type", "application/activity+json")
239 |> post("/inbox", data)
241 assert "ok" == json_response(conn, 200)
242 assert Instances.reachable?(sender_url)
246 describe "/users/:nickname/inbox" do
249 File.read!("test/fixtures/mastodon-post-activity.json")
255 test "it inserts an incoming activity into the database", %{conn: conn, data: data} do
257 data = Map.put(data, "bcc", [user.ap_id])
261 |> assign(:valid_signature, true)
262 |> put_req_header("content-type", "application/activity+json")
263 |> post("/users/#{user.nickname}/inbox", data)
265 assert "ok" == json_response(conn, 200)
267 assert Activity.get_by_ap_id(data["id"])
270 test "it accepts messages from actors that are followed by the user", %{
274 recipient = insert(:user)
275 actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})
277 {:ok, recipient} = User.follow(recipient, actor)
281 |> Map.put("attributedTo", actor.ap_id)
285 |> Map.put("actor", actor.ap_id)
286 |> Map.put("object", object)
290 |> assign(:valid_signature, true)
291 |> put_req_header("content-type", "application/activity+json")
292 |> post("/users/#{recipient.nickname}/inbox", data)
294 assert "ok" == json_response(conn, 200)
296 assert Activity.get_by_ap_id(data["id"])
299 test "it rejects reads from other users", %{conn: conn} do
301 otheruser = insert(:user)
305 |> assign(:user, otheruser)
306 |> put_req_header("accept", "application/activity+json")
307 |> get("/users/#{user.nickname}/inbox")
309 assert json_response(conn, 403)
312 test "it returns a note activity in a collection", %{conn: conn} do
313 note_activity = insert(:direct_note_activity)
314 note_object = Object.normalize(note_activity)
315 user = User.get_cached_by_ap_id(hd(note_activity.data["to"]))
319 |> assign(:user, user)
320 |> put_req_header("accept", "application/activity+json")
321 |> get("/users/#{user.nickname}/inbox")
323 assert response(conn, 200) =~ note_object.data["content"]
326 test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
328 data = Map.put(data, "bcc", [user.ap_id])
330 sender_host = URI.parse(data["actor"]).host
331 Instances.set_consistently_unreachable(sender_host)
332 refute Instances.reachable?(sender_host)
336 |> assign(:valid_signature, true)
337 |> put_req_header("content-type", "application/activity+json")
338 |> post("/users/#{user.nickname}/inbox", data)
340 assert "ok" == json_response(conn, 200)
341 assert Instances.reachable?(sender_host)
344 test "it removes all follower collections but actor's", %{conn: conn} do
345 [actor, recipient] = insert_pair(:user)
348 File.read!("test/fixtures/activitypub-client-post-activity.json")
351 object = Map.put(data["object"], "attributedTo", actor.ap_id)
355 |> Map.put("id", Utils.generate_object_id())
356 |> Map.put("actor", actor.ap_id)
357 |> Map.put("object", object)
359 recipient.follower_address,
360 actor.follower_address
364 recipient.follower_address,
365 "https://www.w3.org/ns/activitystreams#Public"
369 |> assign(:valid_signature, true)
370 |> put_req_header("content-type", "application/activity+json")
371 |> post("/users/#{recipient.nickname}/inbox", data)
372 |> json_response(200)
374 activity = Activity.get_by_ap_id(data["id"])
377 assert actor.follower_address in activity.recipients
378 assert actor.follower_address in activity.data["cc"]
380 refute recipient.follower_address in activity.recipients
381 refute recipient.follower_address in activity.data["cc"]
382 refute recipient.follower_address in activity.data["to"]
386 describe "/users/:nickname/outbox" do
387 test "it will not bomb when there is no activity", %{conn: conn} do
392 |> put_req_header("accept", "application/activity+json")
393 |> get("/users/#{user.nickname}/outbox")
395 result = json_response(conn, 200)
396 assert user.ap_id <> "/outbox" == result["id"]
399 test "it returns a note activity in a collection", %{conn: conn} do
400 note_activity = insert(:note_activity)
401 note_object = Object.normalize(note_activity)
402 user = User.get_cached_by_ap_id(note_activity.data["actor"])
406 |> put_req_header("accept", "application/activity+json")
407 |> get("/users/#{user.nickname}/outbox")
409 assert response(conn, 200) =~ note_object.data["content"]
412 test "it returns an announce activity in a collection", %{conn: conn} do
413 announce_activity = insert(:announce_activity)
414 user = User.get_cached_by_ap_id(announce_activity.data["actor"])
418 |> put_req_header("accept", "application/activity+json")
419 |> get("/users/#{user.nickname}/outbox")
421 assert response(conn, 200) =~ announce_activity.data["object"]
424 test "it rejects posts from other users", %{conn: conn} do
425 data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!()
427 otheruser = insert(:user)
431 |> assign(:user, otheruser)
432 |> put_req_header("content-type", "application/activity+json")
433 |> post("/users/#{user.nickname}/outbox", data)
435 assert json_response(conn, 403)
438 test "it inserts an incoming create activity into the database", %{conn: conn} do
439 data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!()
444 |> assign(:user, user)
445 |> put_req_header("content-type", "application/activity+json")
446 |> post("/users/#{user.nickname}/outbox", data)
448 result = json_response(conn, 201)
449 assert Activity.get_by_ap_id(result["id"])
452 test "it rejects an incoming activity with bogus type", %{conn: conn} do
453 data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!()
458 |> Map.put("type", "BadType")
462 |> assign(:user, user)
463 |> put_req_header("content-type", "application/activity+json")
464 |> post("/users/#{user.nickname}/outbox", data)
466 assert json_response(conn, 400)
469 test "it erects a tombstone when receiving a delete activity", %{conn: conn} do
470 note_activity = insert(:note_activity)
471 note_object = Object.normalize(note_activity)
472 user = User.get_cached_by_ap_id(note_activity.data["actor"])
477 id: note_object.data["id"]
483 |> assign(:user, user)
484 |> put_req_header("content-type", "application/activity+json")
485 |> post("/users/#{user.nickname}/outbox", data)
487 result = json_response(conn, 201)
488 assert Activity.get_by_ap_id(result["id"])
490 assert object = Object.get_by_ap_id(note_object.data["id"])
491 assert object.data["type"] == "Tombstone"
494 test "it rejects delete activity of object from other actor", %{conn: conn} do
495 note_activity = insert(:note_activity)
496 note_object = Object.normalize(note_activity)
502 id: note_object.data["id"]
508 |> assign(:user, user)
509 |> put_req_header("content-type", "application/activity+json")
510 |> post("/users/#{user.nickname}/outbox", data)
512 assert json_response(conn, 400)
515 test "it increases like count when receiving a like action", %{conn: conn} do
516 note_activity = insert(:note_activity)
517 note_object = Object.normalize(note_activity)
518 user = User.get_cached_by_ap_id(note_activity.data["actor"])
523 id: note_object.data["id"]
529 |> assign(:user, user)
530 |> put_req_header("content-type", "application/activity+json")
531 |> post("/users/#{user.nickname}/outbox", data)
533 result = json_response(conn, 201)
534 assert Activity.get_by_ap_id(result["id"])
536 assert object = Object.get_by_ap_id(note_object.data["id"])
537 assert object.data["like_count"] == 1
541 describe "/users/:nickname/followers" do
542 test "it returns the followers in a collection", %{conn: conn} do
544 user_two = insert(:user)
545 User.follow(user, user_two)
549 |> get("/users/#{user_two.nickname}/followers")
550 |> json_response(200)
552 assert result["first"]["orderedItems"] == [user.ap_id]
555 test "it returns returns a uri if the user has 'hide_followers' set", %{conn: conn} do
557 user_two = insert(:user, %{info: %{hide_followers: true}})
558 User.follow(user, user_two)
562 |> get("/users/#{user_two.nickname}/followers")
563 |> json_response(200)
565 assert is_binary(result["first"])
568 test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is not authenticated",
570 user = insert(:user, %{info: %{hide_followers: true}})
574 |> get("/users/#{user.nickname}/followers?page=1")
576 assert result.status == 403
577 assert result.resp_body == ""
580 test "it renders the page, if the user has 'hide_followers' set and the request is authenticated with the same user",
582 user = insert(:user, %{info: %{hide_followers: true}})
583 other_user = insert(:user)
584 {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
588 |> assign(:user, user)
589 |> get("/users/#{user.nickname}/followers?page=1")
590 |> json_response(200)
592 assert result["totalItems"] == 1
593 assert result["orderedItems"] == [other_user.ap_id]
596 test "it works for more than 10 users", %{conn: conn} do
599 Enum.each(1..15, fn _ ->
600 other_user = insert(:user)
601 User.follow(other_user, user)
606 |> get("/users/#{user.nickname}/followers")
607 |> json_response(200)
609 assert length(result["first"]["orderedItems"]) == 10
610 assert result["first"]["totalItems"] == 15
611 assert result["totalItems"] == 15
615 |> get("/users/#{user.nickname}/followers?page=2")
616 |> json_response(200)
618 assert length(result["orderedItems"]) == 5
619 assert result["totalItems"] == 15
623 describe "/users/:nickname/following" do
624 test "it returns the following in a collection", %{conn: conn} do
626 user_two = insert(:user)
627 User.follow(user, user_two)
631 |> get("/users/#{user.nickname}/following")
632 |> json_response(200)
634 assert result["first"]["orderedItems"] == [user_two.ap_id]
637 test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do
638 user = insert(:user, %{info: %{hide_follows: true}})
639 user_two = insert(:user)
640 User.follow(user, user_two)
644 |> get("/users/#{user.nickname}/following")
645 |> json_response(200)
647 assert is_binary(result["first"])
650 test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is not authenticated",
652 user = insert(:user, %{info: %{hide_follows: true}})
656 |> get("/users/#{user.nickname}/following?page=1")
658 assert result.status == 403
659 assert result.resp_body == ""
662 test "it renders the page, if the user has 'hide_follows' set and the request is authenticated with the same user",
664 user = insert(:user, %{info: %{hide_follows: true}})
665 other_user = insert(:user)
666 {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
670 |> assign(:user, user)
671 |> get("/users/#{user.nickname}/following?page=1")
672 |> json_response(200)
674 assert result["totalItems"] == 1
675 assert result["orderedItems"] == [other_user.ap_id]
678 test "it works for more than 10 users", %{conn: conn} do
681 Enum.each(1..15, fn _ ->
682 user = User.get_cached_by_id(user.id)
683 other_user = insert(:user)
684 User.follow(user, other_user)
689 |> get("/users/#{user.nickname}/following")
690 |> json_response(200)
692 assert length(result["first"]["orderedItems"]) == 10
693 assert result["first"]["totalItems"] == 15
694 assert result["totalItems"] == 15
698 |> get("/users/#{user.nickname}/following?page=2")
699 |> json_response(200)
701 assert length(result["orderedItems"]) == 5
702 assert result["totalItems"] == 15