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
17 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
19 config_path = [:instance, :federating]
20 initial_setting = Pleroma.Config.get(config_path)
22 Pleroma.Config.put(config_path, true)
23 on_exit(fn -> Pleroma.Config.put(config_path, initial_setting) end)
29 test "with the relay active, it returns the relay user", %{conn: conn} do
32 |> get(activity_pub_path(conn, :relay))
35 assert res["id"] =~ "/relay"
38 test "with the relay disabled, it returns 404", %{conn: conn} do
39 Pleroma.Config.put([:instance, :allow_relay], false)
42 |> get(activity_pub_path(conn, :relay))
46 Pleroma.Config.put([:instance, :allow_relay], true)
50 describe "/users/:nickname" do
51 test "it returns a json representation of the user with accept application/json", %{
58 |> put_req_header("accept", "application/json")
59 |> get("/users/#{user.nickname}")
61 user = User.get_cached_by_id(user.id)
63 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
66 test "it returns a json representation of the user with accept application/activity+json", %{
73 |> put_req_header("accept", "application/activity+json")
74 |> get("/users/#{user.nickname}")
76 user = User.get_cached_by_id(user.id)
78 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
81 test "it returns a json representation of the user with accept application/ld+json", %{
90 "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
92 |> get("/users/#{user.nickname}")
94 user = User.get_cached_by_id(user.id)
96 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
100 describe "/object/:uuid" do
101 test "it returns a json representation of the object with accept application/json", %{
105 uuid = String.split(note.data["id"], "/") |> List.last()
109 |> put_req_header("accept", "application/json")
110 |> get("/objects/#{uuid}")
112 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
115 test "it returns a json representation of the object with accept application/activity+json",
118 uuid = String.split(note.data["id"], "/") |> List.last()
122 |> put_req_header("accept", "application/activity+json")
123 |> get("/objects/#{uuid}")
125 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
128 test "it returns a json representation of the object with accept application/ld+json", %{
132 uuid = String.split(note.data["id"], "/") |> List.last()
138 "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
140 |> get("/objects/#{uuid}")
142 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
145 test "it returns 404 for non-public messages", %{conn: conn} do
146 note = insert(:direct_note)
147 uuid = String.split(note.data["id"], "/") |> List.last()
151 |> put_req_header("accept", "application/activity+json")
152 |> get("/objects/#{uuid}")
154 assert json_response(conn, 404)
157 test "it returns 404 for tombstone objects", %{conn: conn} do
158 tombstone = insert(:tombstone)
159 uuid = String.split(tombstone.data["id"], "/") |> List.last()
163 |> put_req_header("accept", "application/activity+json")
164 |> get("/objects/#{uuid}")
166 assert json_response(conn, 404)
170 describe "/object/:uuid/likes" do
171 test "it returns the like activities in a collection", %{conn: conn} do
172 like = insert(:like_activity)
173 uuid = String.split(like.data["object"], "/") |> List.last()
177 |> put_req_header("accept", "application/activity+json")
178 |> get("/objects/#{uuid}/likes")
179 |> json_response(200)
181 assert List.first(result["first"]["orderedItems"])["id"] == like.data["id"]
185 describe "/activities/:uuid" do
186 test "it returns a json representation of the activity", %{conn: conn} do
187 activity = insert(:note_activity)
188 uuid = String.split(activity.data["id"], "/") |> List.last()
192 |> put_req_header("accept", "application/activity+json")
193 |> get("/activities/#{uuid}")
195 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: activity})
198 test "it returns 404 for non-public activities", %{conn: conn} do
199 activity = insert(:direct_note_activity)
200 uuid = String.split(activity.data["id"], "/") |> List.last()
204 |> put_req_header("accept", "application/activity+json")
205 |> get("/activities/#{uuid}")
207 assert json_response(conn, 404)
212 test "it inserts an incoming activity into the database", %{conn: conn} do
213 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
217 |> assign(:valid_signature, true)
218 |> put_req_header("content-type", "application/activity+json")
219 |> post("/inbox", data)
221 assert "ok" == json_response(conn, 200)
223 assert Activity.get_by_ap_id(data["id"])
226 test "it clears `unreachable` federation status of the sender", %{conn: conn} do
227 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
229 sender_url = data["actor"]
230 Instances.set_consistently_unreachable(sender_url)
231 refute Instances.reachable?(sender_url)
235 |> assign(:valid_signature, true)
236 |> put_req_header("content-type", "application/activity+json")
237 |> post("/inbox", data)
239 assert "ok" == json_response(conn, 200)
240 assert Instances.reachable?(sender_url)
244 describe "/users/:nickname/inbox" do
247 File.read!("test/fixtures/mastodon-post-activity.json")
253 test "it inserts an incoming activity into the database", %{conn: conn, data: data} do
255 data = Map.put(data, "bcc", [user.ap_id])
259 |> assign(:valid_signature, true)
260 |> put_req_header("content-type", "application/activity+json")
261 |> post("/users/#{user.nickname}/inbox", data)
263 assert "ok" == json_response(conn, 200)
265 assert Activity.get_by_ap_id(data["id"])
268 test "it accepts messages from actors that are followed by the user", %{
272 recipient = insert(:user)
273 actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})
275 {:ok, recipient} = User.follow(recipient, actor)
279 |> Map.put("attributedTo", actor.ap_id)
283 |> Map.put("actor", actor.ap_id)
284 |> Map.put("object", object)
288 |> assign(:valid_signature, true)
289 |> put_req_header("content-type", "application/activity+json")
290 |> post("/users/#{recipient.nickname}/inbox", data)
292 assert "ok" == json_response(conn, 200)
294 assert Activity.get_by_ap_id(data["id"])
297 test "it rejects reads from other users", %{conn: conn} do
299 otheruser = insert(:user)
303 |> assign(:user, otheruser)
304 |> put_req_header("accept", "application/activity+json")
305 |> get("/users/#{user.nickname}/inbox")
307 assert json_response(conn, 403)
310 test "it returns a note activity in a collection", %{conn: conn} do
311 note_activity = insert(:direct_note_activity)
312 user = User.get_cached_by_ap_id(hd(note_activity.data["to"]))
316 |> assign(:user, user)
317 |> put_req_header("accept", "application/activity+json")
318 |> get("/users/#{user.nickname}/inbox")
320 assert response(conn, 200) =~ note_activity.data["object"]["content"]
323 test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
325 data = Map.put(data, "bcc", [user.ap_id])
327 sender_host = URI.parse(data["actor"]).host
328 Instances.set_consistently_unreachable(sender_host)
329 refute Instances.reachable?(sender_host)
333 |> assign(:valid_signature, true)
334 |> put_req_header("content-type", "application/activity+json")
335 |> post("/users/#{user.nickname}/inbox", data)
337 assert "ok" == json_response(conn, 200)
338 assert Instances.reachable?(sender_host)
341 test "it removes all follower collections but actor's", %{conn: conn} do
342 [actor, recipient] = insert_pair(:user)
345 File.read!("test/fixtures/activitypub-client-post-activity.json")
348 object = Map.put(data["object"], "attributedTo", actor.ap_id)
352 |> Map.put("id", Utils.generate_object_id())
353 |> Map.put("actor", actor.ap_id)
354 |> Map.put("object", object)
356 recipient.follower_address,
357 actor.follower_address
361 recipient.follower_address,
362 "https://www.w3.org/ns/activitystreams#Public"
366 |> assign(:valid_signature, true)
367 |> put_req_header("content-type", "application/activity+json")
368 |> post("/users/#{recipient.nickname}/inbox", data)
369 |> json_response(200)
371 activity = Activity.get_by_ap_id(data["id"])
374 assert actor.follower_address in activity.recipients
375 assert actor.follower_address in activity.data["cc"]
377 refute recipient.follower_address in activity.recipients
378 refute recipient.follower_address in activity.data["cc"]
379 refute recipient.follower_address in activity.data["to"]
383 describe "/users/:nickname/outbox" do
384 test "it will not bomb when there is no activity", %{conn: conn} do
389 |> put_req_header("accept", "application/activity+json")
390 |> get("/users/#{user.nickname}/outbox")
392 result = json_response(conn, 200)
393 assert user.ap_id <> "/outbox" == result["id"]
396 test "it returns a note activity in a collection", %{conn: conn} do
397 note_activity = insert(:note_activity)
398 user = User.get_cached_by_ap_id(note_activity.data["actor"])
402 |> put_req_header("accept", "application/activity+json")
403 |> get("/users/#{user.nickname}/outbox")
405 assert response(conn, 200) =~ note_activity.data["object"]["content"]
408 test "it returns an announce activity in a collection", %{conn: conn} do
409 announce_activity = insert(:announce_activity)
410 user = User.get_cached_by_ap_id(announce_activity.data["actor"])
414 |> put_req_header("accept", "application/activity+json")
415 |> get("/users/#{user.nickname}/outbox")
417 assert response(conn, 200) =~ announce_activity.data["object"]
420 test "it rejects posts from other users", %{conn: conn} do
421 data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!()
423 otheruser = insert(:user)
427 |> assign(:user, otheruser)
428 |> put_req_header("content-type", "application/activity+json")
429 |> post("/users/#{user.nickname}/outbox", data)
431 assert json_response(conn, 403)
434 test "it inserts an incoming create activity into the database", %{conn: conn} do
435 data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!()
440 |> assign(:user, user)
441 |> put_req_header("content-type", "application/activity+json")
442 |> post("/users/#{user.nickname}/outbox", data)
444 result = json_response(conn, 201)
445 assert Activity.get_by_ap_id(result["id"])
448 test "it rejects an incoming activity with bogus type", %{conn: conn} do
449 data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!()
454 |> Map.put("type", "BadType")
458 |> assign(:user, user)
459 |> put_req_header("content-type", "application/activity+json")
460 |> post("/users/#{user.nickname}/outbox", data)
462 assert json_response(conn, 400)
465 test "it erects a tombstone when receiving a delete activity", %{conn: conn} do
466 note_activity = insert(:note_activity)
467 user = User.get_cached_by_ap_id(note_activity.data["actor"])
472 id: note_activity.data["object"]["id"]
478 |> assign(:user, user)
479 |> put_req_header("content-type", "application/activity+json")
480 |> post("/users/#{user.nickname}/outbox", data)
482 result = json_response(conn, 201)
483 assert Activity.get_by_ap_id(result["id"])
485 object = Object.get_by_ap_id(note_activity.data["object"]["id"])
487 assert object.data["type"] == "Tombstone"
490 test "it rejects delete activity of object from other actor", %{conn: conn} do
491 note_activity = insert(:note_activity)
497 id: note_activity.data["object"]["id"]
503 |> assign(:user, user)
504 |> put_req_header("content-type", "application/activity+json")
505 |> post("/users/#{user.nickname}/outbox", data)
507 assert json_response(conn, 400)
510 test "it increases like count when receiving a like action", %{conn: conn} do
511 note_activity = insert(:note_activity)
512 user = User.get_cached_by_ap_id(note_activity.data["actor"])
517 id: note_activity.data["object"]["id"]
523 |> assign(:user, user)
524 |> put_req_header("content-type", "application/activity+json")
525 |> post("/users/#{user.nickname}/outbox", data)
527 result = json_response(conn, 201)
528 assert Activity.get_by_ap_id(result["id"])
530 object = Object.get_by_ap_id(note_activity.data["object"]["id"])
532 assert object.data["like_count"] == 1
536 describe "/users/:nickname/followers" do
537 test "it returns the followers in a collection", %{conn: conn} do
539 user_two = insert(:user)
540 User.follow(user, user_two)
544 |> get("/users/#{user_two.nickname}/followers")
545 |> json_response(200)
547 assert result["first"]["orderedItems"] == [user.ap_id]
550 test "it returns returns empty if the user has 'hide_followers' set", %{conn: conn} do
552 user_two = insert(:user, %{info: %{hide_followers: true}})
553 User.follow(user, user_two)
557 |> get("/users/#{user_two.nickname}/followers")
558 |> json_response(200)
560 assert result["first"]["orderedItems"] == []
561 assert result["totalItems"] == 0
564 test "it works for more than 10 users", %{conn: conn} do
567 Enum.each(1..15, fn _ ->
568 other_user = insert(:user)
569 User.follow(other_user, user)
574 |> get("/users/#{user.nickname}/followers")
575 |> json_response(200)
577 assert length(result["first"]["orderedItems"]) == 10
578 assert result["first"]["totalItems"] == 15
579 assert result["totalItems"] == 15
583 |> get("/users/#{user.nickname}/followers?page=2")
584 |> json_response(200)
586 assert length(result["orderedItems"]) == 5
587 assert result["totalItems"] == 15
591 describe "/users/:nickname/following" do
592 test "it returns the following in a collection", %{conn: conn} do
594 user_two = insert(:user)
595 User.follow(user, user_two)
599 |> get("/users/#{user.nickname}/following")
600 |> json_response(200)
602 assert result["first"]["orderedItems"] == [user_two.ap_id]
605 test "it returns returns empty if the user has 'hide_follows' set", %{conn: conn} do
606 user = insert(:user, %{info: %{hide_follows: true}})
607 user_two = insert(:user)
608 User.follow(user, user_two)
612 |> get("/users/#{user.nickname}/following")
613 |> json_response(200)
615 assert result["first"]["orderedItems"] == []
616 assert result["totalItems"] == 0
619 test "it works for more than 10 users", %{conn: conn} do
622 Enum.each(1..15, fn _ ->
623 user = User.get_cached_by_id(user.id)
624 other_user = insert(:user)
625 User.follow(user, other_user)
630 |> get("/users/#{user.nickname}/following")
631 |> json_response(200)
633 assert length(result["first"]["orderedItems"]) == 10
634 assert result["first"]["totalItems"] == 15
635 assert result["totalItems"] == 15
639 |> get("/users/#{user.nickname}/following?page=2")
640 |> json_response(200)
642 assert length(result["orderedItems"]) == 5
643 assert result["totalItems"] == 15