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
8 alias Pleroma.Web.ActivityPub.UserView
9 alias Pleroma.Web.ActivityPub.ObjectView
12 alias Pleroma.Activity
14 alias Pleroma.Instances
17 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
22 test "with the relay active, it returns the relay user", %{conn: conn} do
25 |> get(activity_pub_path(conn, :relay))
28 assert res["id"] =~ "/relay"
31 test "with the relay disabled, it returns 404", %{conn: conn} do
32 Pleroma.Config.put([:instance, :allow_relay], false)
35 |> get(activity_pub_path(conn, :relay))
39 Pleroma.Config.put([:instance, :allow_relay], true)
43 describe "/users/:nickname" do
44 test "it returns a json representation of the user", %{conn: conn} do
49 |> put_req_header("accept", "application/activity+json")
50 |> get("/users/#{user.nickname}")
52 user = Repo.get(User, user.id)
54 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
58 describe "/object/:uuid" do
59 test "it returns a json representation of the object", %{conn: conn} do
61 uuid = String.split(note.data["id"], "/") |> List.last()
65 |> put_req_header("accept", "application/activity+json")
66 |> get("/objects/#{uuid}")
68 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
71 test "it returns 404 for non-public messages", %{conn: conn} do
72 note = insert(:direct_note)
73 uuid = String.split(note.data["id"], "/") |> List.last()
77 |> put_req_header("accept", "application/activity+json")
78 |> get("/objects/#{uuid}")
80 assert json_response(conn, 404)
83 test "it returns 404 for tombstone objects", %{conn: conn} do
84 tombstone = insert(:tombstone)
85 uuid = String.split(tombstone.data["id"], "/") |> List.last()
89 |> put_req_header("accept", "application/activity+json")
90 |> get("/objects/#{uuid}")
92 assert json_response(conn, 404)
96 describe "/object/:uuid/likes" do
97 test "it returns the like activities in a collection", %{conn: conn} do
98 like = insert(:like_activity)
99 uuid = String.split(like.data["object"], "/") |> List.last()
103 |> put_req_header("accept", "application/activity+json")
104 |> get("/objects/#{uuid}/likes")
105 |> json_response(200)
107 assert List.first(result["first"]["orderedItems"])["id"] == like.data["id"]
111 describe "/activities/:uuid" do
112 test "it returns a json representation of the activity", %{conn: conn} do
113 activity = insert(:note_activity)
114 uuid = String.split(activity.data["id"], "/") |> List.last()
118 |> put_req_header("accept", "application/activity+json")
119 |> get("/activities/#{uuid}")
121 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: activity})
124 test "it returns 404 for non-public activities", %{conn: conn} do
125 activity = insert(:direct_note_activity)
126 uuid = String.split(activity.data["id"], "/") |> List.last()
130 |> put_req_header("accept", "application/activity+json")
131 |> get("/activities/#{uuid}")
133 assert json_response(conn, 404)
138 test "it inserts an incoming activity into the database", %{conn: conn} do
139 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
143 |> assign(:valid_signature, true)
144 |> put_req_header("content-type", "application/activity+json")
145 |> post("/inbox", data)
147 assert "ok" == json_response(conn, 200)
149 assert Activity.get_by_ap_id(data["id"])
152 test "it clears `unreachable` federation status of the sender", %{conn: conn} do
153 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
155 sender_url = data["actor"]
156 Instances.set_consistently_unreachable(sender_url)
157 refute Instances.reachable?(sender_url)
161 |> assign(:valid_signature, true)
162 |> put_req_header("content-type", "application/activity+json")
163 |> post("/inbox", data)
165 assert "ok" == json_response(conn, 200)
166 assert Instances.reachable?(sender_url)
170 describe "/users/:nickname/inbox" do
171 test "it inserts an incoming activity into the database", %{conn: conn} do
175 File.read!("test/fixtures/mastodon-post-activity.json")
177 |> Map.put("bcc", [user.ap_id])
181 |> assign(:valid_signature, true)
182 |> put_req_header("content-type", "application/activity+json")
183 |> post("/users/#{user.nickname}/inbox", data)
185 assert "ok" == json_response(conn, 200)
187 assert Activity.get_by_ap_id(data["id"])
190 test "it rejects reads from other users", %{conn: conn} do
192 otheruser = insert(:user)
196 |> assign(:user, otheruser)
197 |> put_req_header("accept", "application/activity+json")
198 |> get("/users/#{user.nickname}/inbox")
200 assert json_response(conn, 403)
203 test "it returns a note activity in a collection", %{conn: conn} do
204 note_activity = insert(:direct_note_activity)
205 user = User.get_cached_by_ap_id(hd(note_activity.data["to"]))
209 |> assign(:user, user)
210 |> put_req_header("accept", "application/activity+json")
211 |> get("/users/#{user.nickname}/inbox")
213 assert response(conn, 200) =~ note_activity.data["object"]["content"]
216 test "it clears `unreachable` federation status of the sender", %{conn: conn} do
220 File.read!("test/fixtures/mastodon-post-activity.json")
222 |> Map.put("bcc", [user.ap_id])
224 sender_host = URI.parse(data["actor"]).host
225 Instances.set_consistently_unreachable(sender_host)
226 refute Instances.reachable?(sender_host)
230 |> assign(:valid_signature, true)
231 |> put_req_header("content-type", "application/activity+json")
232 |> post("/users/#{user.nickname}/inbox", data)
234 assert "ok" == json_response(conn, 200)
235 assert Instances.reachable?(sender_host)
239 describe "/users/:nickname/outbox" do
240 test "it returns a note activity in a collection", %{conn: conn} do
241 note_activity = insert(:note_activity)
242 user = User.get_cached_by_ap_id(note_activity.data["actor"])
246 |> put_req_header("accept", "application/activity+json")
247 |> get("/users/#{user.nickname}/outbox")
249 assert response(conn, 200) =~ note_activity.data["object"]["content"]
252 test "it returns an announce activity in a collection", %{conn: conn} do
253 announce_activity = insert(:announce_activity)
254 user = User.get_cached_by_ap_id(announce_activity.data["actor"])
258 |> put_req_header("accept", "application/activity+json")
259 |> get("/users/#{user.nickname}/outbox")
261 assert response(conn, 200) =~ announce_activity.data["object"]
264 test "it rejects posts from other users", %{conn: conn} do
265 data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!()
267 otheruser = insert(:user)
271 |> assign(:user, otheruser)
272 |> put_req_header("content-type", "application/activity+json")
273 |> post("/users/#{user.nickname}/outbox", data)
275 assert json_response(conn, 403)
278 test "it inserts an incoming create activity into the database", %{conn: conn} do
279 data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!()
284 |> assign(:user, user)
285 |> put_req_header("content-type", "application/activity+json")
286 |> post("/users/#{user.nickname}/outbox", data)
288 result = json_response(conn, 201)
289 assert Activity.get_by_ap_id(result["id"])
292 test "it rejects an incoming activity with bogus type", %{conn: conn} do
293 data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!()
298 |> Map.put("type", "BadType")
302 |> assign(:user, user)
303 |> put_req_header("content-type", "application/activity+json")
304 |> post("/users/#{user.nickname}/outbox", data)
306 assert json_response(conn, 400)
309 test "it erects a tombstone when receiving a delete activity", %{conn: conn} do
310 note_activity = insert(:note_activity)
311 user = User.get_cached_by_ap_id(note_activity.data["actor"])
316 id: note_activity.data["object"]["id"]
322 |> assign(:user, user)
323 |> put_req_header("content-type", "application/activity+json")
324 |> post("/users/#{user.nickname}/outbox", data)
326 result = json_response(conn, 201)
327 assert Activity.get_by_ap_id(result["id"])
329 object = Object.get_by_ap_id(note_activity.data["object"]["id"])
331 assert object.data["type"] == "Tombstone"
334 test "it rejects delete activity of object from other actor", %{conn: conn} do
335 note_activity = insert(:note_activity)
341 id: note_activity.data["object"]["id"]
347 |> assign(:user, user)
348 |> put_req_header("content-type", "application/activity+json")
349 |> post("/users/#{user.nickname}/outbox", data)
351 assert json_response(conn, 400)
354 test "it increases like count when receiving a like action", %{conn: conn} do
355 note_activity = insert(:note_activity)
356 user = User.get_cached_by_ap_id(note_activity.data["actor"])
361 id: note_activity.data["object"]["id"]
367 |> assign(:user, user)
368 |> put_req_header("content-type", "application/activity+json")
369 |> post("/users/#{user.nickname}/outbox", data)
371 result = json_response(conn, 201)
372 assert Activity.get_by_ap_id(result["id"])
374 object = Object.get_by_ap_id(note_activity.data["object"]["id"])
376 assert object.data["like_count"] == 1
380 describe "/users/:nickname/followers" do
381 test "it returns the followers in a collection", %{conn: conn} do
383 user_two = insert(:user)
384 User.follow(user, user_two)
388 |> get("/users/#{user_two.nickname}/followers")
389 |> json_response(200)
391 assert result["first"]["orderedItems"] == [user.ap_id]
394 test "it returns returns empty if the user has 'hide_followers' set", %{conn: conn} do
396 user_two = insert(:user, %{info: %{hide_followers: true}})
397 User.follow(user, user_two)
401 |> get("/users/#{user_two.nickname}/followers")
402 |> json_response(200)
404 assert result["first"]["orderedItems"] == []
405 assert result["totalItems"] == 0
408 test "it works for more than 10 users", %{conn: conn} do
411 Enum.each(1..15, fn _ ->
412 other_user = insert(:user)
413 User.follow(other_user, user)
418 |> get("/users/#{user.nickname}/followers")
419 |> json_response(200)
421 assert length(result["first"]["orderedItems"]) == 10
422 assert result["first"]["totalItems"] == 15
423 assert result["totalItems"] == 15
427 |> get("/users/#{user.nickname}/followers?page=2")
428 |> json_response(200)
430 assert length(result["orderedItems"]) == 5
431 assert result["totalItems"] == 15
435 describe "/users/:nickname/following" do
436 test "it returns the following in a collection", %{conn: conn} do
438 user_two = insert(:user)
439 User.follow(user, user_two)
443 |> get("/users/#{user.nickname}/following")
444 |> json_response(200)
446 assert result["first"]["orderedItems"] == [user_two.ap_id]
449 test "it returns returns empty if the user has 'hide_follows' set", %{conn: conn} do
450 user = insert(:user, %{info: %{hide_follows: true}})
451 user_two = insert(:user)
452 User.follow(user, user_two)
456 |> get("/users/#{user.nickname}/following")
457 |> json_response(200)
459 assert result["first"]["orderedItems"] == []
460 assert result["totalItems"] == 0
463 test "it works for more than 10 users", %{conn: conn} do
466 Enum.each(1..15, fn _ ->
467 user = Repo.get(User, user.id)
468 other_user = insert(:user)
469 User.follow(user, other_user)
474 |> get("/users/#{user.nickname}/following")
475 |> json_response(200)
477 assert length(result["first"]["orderedItems"]) == 10
478 assert result["first"]["totalItems"] == 15
479 assert result["totalItems"] == 15
483 |> get("/users/#{user.nickname}/following?page=2")
484 |> json_response(200)
486 assert length(result["orderedItems"]) == 5
487 assert result["totalItems"] == 15