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, ObjectView}
9 alias Pleroma.{Object, Repo, Activity, User, Instances}
12 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
17 test "with the relay active, it returns the relay user", %{conn: conn} do
20 |> get(activity_pub_path(conn, :relay))
23 assert res["id"] =~ "/relay"
26 test "with the relay disabled, it returns 404", %{conn: conn} do
27 Pleroma.Config.put([:instance, :allow_relay], false)
30 |> get(activity_pub_path(conn, :relay))
34 Pleroma.Config.put([:instance, :allow_relay], true)
38 describe "/users/:nickname" do
39 test "it returns a json representation of the user", %{conn: conn} do
44 |> put_req_header("accept", "application/activity+json")
45 |> get("/users/#{user.nickname}")
47 user = Repo.get(User, user.id)
49 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
53 describe "/object/:uuid" do
54 test "it returns a json representation of the object", %{conn: conn} do
56 uuid = String.split(note.data["id"], "/") |> List.last()
60 |> put_req_header("accept", "application/activity+json")
61 |> get("/objects/#{uuid}")
63 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
66 test "it returns 404 for non-public messages", %{conn: conn} do
67 note = insert(:direct_note)
68 uuid = String.split(note.data["id"], "/") |> List.last()
72 |> put_req_header("accept", "application/activity+json")
73 |> get("/objects/#{uuid}")
75 assert json_response(conn, 404)
78 test "it returns 404 for tombstone objects", %{conn: conn} do
79 tombstone = insert(:tombstone)
80 uuid = String.split(tombstone.data["id"], "/") |> List.last()
84 |> put_req_header("accept", "application/activity+json")
85 |> get("/objects/#{uuid}")
87 assert json_response(conn, 404)
91 describe "/object/:uuid/likes" do
92 test "it returns the like activities in a collection", %{conn: conn} do
93 like = insert(:like_activity)
94 uuid = String.split(like.data["object"], "/") |> List.last()
98 |> put_req_header("accept", "application/activity+json")
99 |> get("/objects/#{uuid}/likes")
100 |> json_response(200)
102 assert List.first(result["first"]["orderedItems"])["id"] == like.data["id"]
106 describe "/activities/:uuid" do
107 test "it returns a json representation of the activity", %{conn: conn} do
108 activity = insert(:note_activity)
109 uuid = String.split(activity.data["id"], "/") |> List.last()
113 |> put_req_header("accept", "application/activity+json")
114 |> get("/activities/#{uuid}")
116 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: activity})
119 test "it returns 404 for non-public activities", %{conn: conn} do
120 activity = insert(:direct_note_activity)
121 uuid = String.split(activity.data["id"], "/") |> List.last()
125 |> put_req_header("accept", "application/activity+json")
126 |> get("/activities/#{uuid}")
128 assert json_response(conn, 404)
133 test "it inserts an incoming activity into the database", %{conn: conn} do
134 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
138 |> assign(:valid_signature, true)
139 |> put_req_header("content-type", "application/activity+json")
140 |> post("/inbox", data)
142 assert "ok" == json_response(conn, 200)
144 assert Activity.get_by_ap_id(data["id"])
147 test "it clears `unreachable` federation status of the sender", %{conn: conn} do
148 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
150 sender_url = data["actor"]
151 Instances.set_consistently_unreachable(sender_url)
152 refute Instances.reachable?(sender_url)
156 |> assign(:valid_signature, true)
157 |> put_req_header("content-type", "application/activity+json")
158 |> post("/inbox", data)
160 assert "ok" == json_response(conn, 200)
161 assert Instances.reachable?(sender_url)
165 describe "/users/:nickname/inbox" do
166 test "it inserts an incoming activity into the database", %{conn: conn} do
170 File.read!("test/fixtures/mastodon-post-activity.json")
172 |> Map.put("bcc", [user.ap_id])
176 |> assign(:valid_signature, true)
177 |> put_req_header("content-type", "application/activity+json")
178 |> post("/users/#{user.nickname}/inbox", data)
180 assert "ok" == json_response(conn, 200)
182 assert Activity.get_by_ap_id(data["id"])
185 test "it rejects reads from other users", %{conn: conn} do
187 otheruser = insert(:user)
191 |> assign(:user, otheruser)
192 |> put_req_header("accept", "application/activity+json")
193 |> get("/users/#{user.nickname}/inbox")
195 assert json_response(conn, 403)
198 test "it returns a note activity in a collection", %{conn: conn} do
199 note_activity = insert(:direct_note_activity)
200 user = User.get_cached_by_ap_id(hd(note_activity.data["to"]))
204 |> assign(:user, user)
205 |> put_req_header("accept", "application/activity+json")
206 |> get("/users/#{user.nickname}/inbox")
208 assert response(conn, 200) =~ note_activity.data["object"]["content"]
211 test "it clears `unreachable` federation status of the sender", %{conn: conn} do
215 File.read!("test/fixtures/mastodon-post-activity.json")
217 |> Map.put("bcc", [user.ap_id])
219 sender_host = URI.parse(data["actor"]).host
220 Instances.set_consistently_unreachable(sender_host)
221 refute Instances.reachable?(sender_host)
225 |> assign(:valid_signature, true)
226 |> put_req_header("content-type", "application/activity+json")
227 |> post("/users/#{user.nickname}/inbox", data)
229 assert "ok" == json_response(conn, 200)
230 assert Instances.reachable?(sender_host)
234 describe "/users/:nickname/outbox" do
235 test "it returns a note activity in a collection", %{conn: conn} do
236 note_activity = insert(:note_activity)
237 user = User.get_cached_by_ap_id(note_activity.data["actor"])
241 |> put_req_header("accept", "application/activity+json")
242 |> get("/users/#{user.nickname}/outbox")
244 assert response(conn, 200) =~ note_activity.data["object"]["content"]
247 test "it returns an announce activity in a collection", %{conn: conn} do
248 announce_activity = insert(:announce_activity)
249 user = User.get_cached_by_ap_id(announce_activity.data["actor"])
253 |> put_req_header("accept", "application/activity+json")
254 |> get("/users/#{user.nickname}/outbox")
256 assert response(conn, 200) =~ announce_activity.data["object"]
259 test "it rejects posts from other users", %{conn: conn} do
260 data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!()
262 otheruser = insert(:user)
266 |> assign(:user, otheruser)
267 |> put_req_header("content-type", "application/activity+json")
268 |> post("/users/#{user.nickname}/outbox", data)
270 assert json_response(conn, 403)
273 test "it inserts an incoming create activity into the database", %{conn: conn} do
274 data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!()
279 |> assign(:user, user)
280 |> put_req_header("content-type", "application/activity+json")
281 |> post("/users/#{user.nickname}/outbox", data)
283 result = json_response(conn, 201)
284 assert Activity.get_by_ap_id(result["id"])
287 test "it rejects an incoming activity with bogus type", %{conn: conn} do
288 data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!()
293 |> Map.put("type", "BadType")
297 |> assign(:user, user)
298 |> put_req_header("content-type", "application/activity+json")
299 |> post("/users/#{user.nickname}/outbox", data)
301 assert json_response(conn, 400)
304 test "it erects a tombstone when receiving a delete activity", %{conn: conn} do
305 note_activity = insert(:note_activity)
306 user = User.get_cached_by_ap_id(note_activity.data["actor"])
311 id: note_activity.data["object"]["id"]
317 |> assign(:user, user)
318 |> put_req_header("content-type", "application/activity+json")
319 |> post("/users/#{user.nickname}/outbox", data)
321 result = json_response(conn, 201)
322 assert Activity.get_by_ap_id(result["id"])
324 object = Object.get_by_ap_id(note_activity.data["object"]["id"])
326 assert object.data["type"] == "Tombstone"
329 test "it rejects delete activity of object from other actor", %{conn: conn} do
330 note_activity = insert(:note_activity)
336 id: note_activity.data["object"]["id"]
342 |> assign(:user, user)
343 |> put_req_header("content-type", "application/activity+json")
344 |> post("/users/#{user.nickname}/outbox", data)
346 assert json_response(conn, 400)
349 test "it increases like count when receiving a like action", %{conn: conn} do
350 note_activity = insert(:note_activity)
351 user = User.get_cached_by_ap_id(note_activity.data["actor"])
356 id: note_activity.data["object"]["id"]
362 |> assign(:user, user)
363 |> put_req_header("content-type", "application/activity+json")
364 |> post("/users/#{user.nickname}/outbox", data)
366 result = json_response(conn, 201)
367 assert Activity.get_by_ap_id(result["id"])
369 object = Object.get_by_ap_id(note_activity.data["object"]["id"])
371 assert object.data["like_count"] == 1
375 describe "/users/:nickname/followers" do
376 test "it returns the followers in a collection", %{conn: conn} do
378 user_two = insert(:user)
379 User.follow(user, user_two)
383 |> get("/users/#{user_two.nickname}/followers")
384 |> json_response(200)
386 assert result["first"]["orderedItems"] == [user.ap_id]
389 test "it returns returns empty if the user has 'hide_network' set", %{conn: conn} do
391 user_two = insert(:user, %{info: %{hide_network: true}})
392 User.follow(user, user_two)
396 |> get("/users/#{user_two.nickname}/followers")
397 |> json_response(200)
399 assert result["first"]["orderedItems"] == []
400 assert result["totalItems"] == 1
403 test "it works for more than 10 users", %{conn: conn} do
406 Enum.each(1..15, fn _ ->
407 other_user = insert(:user)
408 User.follow(other_user, user)
413 |> get("/users/#{user.nickname}/followers")
414 |> json_response(200)
416 assert length(result["first"]["orderedItems"]) == 10
417 assert result["first"]["totalItems"] == 15
418 assert result["totalItems"] == 15
422 |> get("/users/#{user.nickname}/followers?page=2")
423 |> json_response(200)
425 assert length(result["orderedItems"]) == 5
426 assert result["totalItems"] == 15
430 describe "/users/:nickname/following" do
431 test "it returns the following in a collection", %{conn: conn} do
433 user_two = insert(:user)
434 User.follow(user, user_two)
438 |> get("/users/#{user.nickname}/following")
439 |> json_response(200)
441 assert result["first"]["orderedItems"] == [user_two.ap_id]
444 test "it returns returns empty if the user has 'hide_network' set", %{conn: conn} do
445 user = insert(:user, %{info: %{hide_network: true}})
446 user_two = insert(:user)
447 User.follow(user, user_two)
451 |> get("/users/#{user.nickname}/following")
452 |> json_response(200)
454 assert result["first"]["orderedItems"] == []
455 assert result["totalItems"] == 1
458 test "it works for more than 10 users", %{conn: conn} do
461 Enum.each(1..15, fn _ ->
462 user = Repo.get(User, user.id)
463 other_user = insert(:user)
464 User.follow(user, other_user)
469 |> get("/users/#{user.nickname}/following")
470 |> json_response(200)
472 assert length(result["first"]["orderedItems"]) == 10
473 assert result["first"]["totalItems"] == 15
474 assert result["totalItems"] == 15
478 |> get("/users/#{user.nickname}/following?page=2")
479 |> json_response(200)
481 assert length(result["orderedItems"]) == 5
482 assert result["totalItems"] == 15