Merge branch 'develop' into 'docs/add-clients-to-ex_doc'
[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.Web.ActivityPub.UserView
9 alias Pleroma.Web.ActivityPub.ObjectView
10 alias Pleroma.Object
11 alias Pleroma.Repo
12 alias Pleroma.Activity
13 alias Pleroma.User
14 alias Pleroma.Instances
15
16 setup_all do
17 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
18 :ok
19 end
20
21 describe "/relay" do
22 test "with the relay active, it returns the relay user", %{conn: conn} do
23 res =
24 conn
25 |> get(activity_pub_path(conn, :relay))
26 |> json_response(200)
27
28 assert res["id"] =~ "/relay"
29 end
30
31 test "with the relay disabled, it returns 404", %{conn: conn} do
32 Pleroma.Config.put([:instance, :allow_relay], false)
33
34 conn
35 |> get(activity_pub_path(conn, :relay))
36 |> json_response(404)
37 |> assert
38
39 Pleroma.Config.put([:instance, :allow_relay], true)
40 end
41 end
42
43 describe "/users/:nickname" do
44 test "it returns a json representation of the user", %{conn: conn} do
45 user = insert(:user)
46
47 conn =
48 conn
49 |> put_req_header("accept", "application/activity+json")
50 |> get("/users/#{user.nickname}")
51
52 user = Repo.get(User, user.id)
53
54 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
55 end
56 end
57
58 describe "/object/:uuid" do
59 test "it returns a json representation of the object", %{conn: conn} do
60 note = insert(:note)
61 uuid = String.split(note.data["id"], "/") |> List.last()
62
63 conn =
64 conn
65 |> put_req_header("accept", "application/activity+json")
66 |> get("/objects/#{uuid}")
67
68 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
69 end
70
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()
74
75 conn =
76 conn
77 |> put_req_header("accept", "application/activity+json")
78 |> get("/objects/#{uuid}")
79
80 assert json_response(conn, 404)
81 end
82
83 test "it returns 404 for tombstone objects", %{conn: conn} do
84 tombstone = insert(:tombstone)
85 uuid = String.split(tombstone.data["id"], "/") |> List.last()
86
87 conn =
88 conn
89 |> put_req_header("accept", "application/activity+json")
90 |> get("/objects/#{uuid}")
91
92 assert json_response(conn, 404)
93 end
94 end
95
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()
100
101 result =
102 conn
103 |> put_req_header("accept", "application/activity+json")
104 |> get("/objects/#{uuid}/likes")
105 |> json_response(200)
106
107 assert List.first(result["first"]["orderedItems"])["id"] == like.data["id"]
108 end
109 end
110
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()
115
116 conn =
117 conn
118 |> put_req_header("accept", "application/activity+json")
119 |> get("/activities/#{uuid}")
120
121 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: activity})
122 end
123
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()
127
128 conn =
129 conn
130 |> put_req_header("accept", "application/activity+json")
131 |> get("/activities/#{uuid}")
132
133 assert json_response(conn, 404)
134 end
135 end
136
137 describe "/inbox" do
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!()
140
141 conn =
142 conn
143 |> assign(:valid_signature, true)
144 |> put_req_header("content-type", "application/activity+json")
145 |> post("/inbox", data)
146
147 assert "ok" == json_response(conn, 200)
148 :timer.sleep(500)
149 assert Activity.get_by_ap_id(data["id"])
150 end
151
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!()
154
155 sender_url = data["actor"]
156 Instances.set_consistently_unreachable(sender_url)
157 refute Instances.reachable?(sender_url)
158
159 conn =
160 conn
161 |> assign(:valid_signature, true)
162 |> put_req_header("content-type", "application/activity+json")
163 |> post("/inbox", data)
164
165 assert "ok" == json_response(conn, 200)
166 assert Instances.reachable?(sender_url)
167 end
168 end
169
170 describe "/users/:nickname/inbox" do
171 test "it inserts an incoming activity into the database", %{conn: conn} do
172 user = insert(:user)
173
174 data =
175 File.read!("test/fixtures/mastodon-post-activity.json")
176 |> Poison.decode!()
177 |> Map.put("bcc", [user.ap_id])
178
179 conn =
180 conn
181 |> assign(:valid_signature, true)
182 |> put_req_header("content-type", "application/activity+json")
183 |> post("/users/#{user.nickname}/inbox", data)
184
185 assert "ok" == json_response(conn, 200)
186 :timer.sleep(500)
187 assert Activity.get_by_ap_id(data["id"])
188 end
189
190 test "it rejects reads from other users", %{conn: conn} do
191 user = insert(:user)
192 otheruser = insert(:user)
193
194 conn =
195 conn
196 |> assign(:user, otheruser)
197 |> put_req_header("accept", "application/activity+json")
198 |> get("/users/#{user.nickname}/inbox")
199
200 assert json_response(conn, 403)
201 end
202
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"]))
206
207 conn =
208 conn
209 |> assign(:user, user)
210 |> put_req_header("accept", "application/activity+json")
211 |> get("/users/#{user.nickname}/inbox")
212
213 assert response(conn, 200) =~ note_activity.data["object"]["content"]
214 end
215
216 test "it clears `unreachable` federation status of the sender", %{conn: conn} do
217 user = insert(:user)
218
219 data =
220 File.read!("test/fixtures/mastodon-post-activity.json")
221 |> Poison.decode!()
222 |> Map.put("bcc", [user.ap_id])
223
224 sender_host = URI.parse(data["actor"]).host
225 Instances.set_consistently_unreachable(sender_host)
226 refute Instances.reachable?(sender_host)
227
228 conn =
229 conn
230 |> assign(:valid_signature, true)
231 |> put_req_header("content-type", "application/activity+json")
232 |> post("/users/#{user.nickname}/inbox", data)
233
234 assert "ok" == json_response(conn, 200)
235 assert Instances.reachable?(sender_host)
236 end
237 end
238
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"])
243
244 conn =
245 conn
246 |> put_req_header("accept", "application/activity+json")
247 |> get("/users/#{user.nickname}/outbox")
248
249 assert response(conn, 200) =~ note_activity.data["object"]["content"]
250 end
251
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"])
255
256 conn =
257 conn
258 |> put_req_header("accept", "application/activity+json")
259 |> get("/users/#{user.nickname}/outbox")
260
261 assert response(conn, 200) =~ announce_activity.data["object"]
262 end
263
264 test "it rejects posts from other users", %{conn: conn} do
265 data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!()
266 user = insert(:user)
267 otheruser = insert(:user)
268
269 conn =
270 conn
271 |> assign(:user, otheruser)
272 |> put_req_header("content-type", "application/activity+json")
273 |> post("/users/#{user.nickname}/outbox", data)
274
275 assert json_response(conn, 403)
276 end
277
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!()
280 user = insert(:user)
281
282 conn =
283 conn
284 |> assign(:user, user)
285 |> put_req_header("content-type", "application/activity+json")
286 |> post("/users/#{user.nickname}/outbox", data)
287
288 result = json_response(conn, 201)
289 assert Activity.get_by_ap_id(result["id"])
290 end
291
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!()
294 user = insert(:user)
295
296 data =
297 data
298 |> Map.put("type", "BadType")
299
300 conn =
301 conn
302 |> assign(:user, user)
303 |> put_req_header("content-type", "application/activity+json")
304 |> post("/users/#{user.nickname}/outbox", data)
305
306 assert json_response(conn, 400)
307 end
308
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"])
312
313 data = %{
314 type: "Delete",
315 object: %{
316 id: note_activity.data["object"]["id"]
317 }
318 }
319
320 conn =
321 conn
322 |> assign(:user, user)
323 |> put_req_header("content-type", "application/activity+json")
324 |> post("/users/#{user.nickname}/outbox", data)
325
326 result = json_response(conn, 201)
327 assert Activity.get_by_ap_id(result["id"])
328
329 object = Object.get_by_ap_id(note_activity.data["object"]["id"])
330 assert object
331 assert object.data["type"] == "Tombstone"
332 end
333
334 test "it rejects delete activity of object from other actor", %{conn: conn} do
335 note_activity = insert(:note_activity)
336 user = insert(:user)
337
338 data = %{
339 type: "Delete",
340 object: %{
341 id: note_activity.data["object"]["id"]
342 }
343 }
344
345 conn =
346 conn
347 |> assign(:user, user)
348 |> put_req_header("content-type", "application/activity+json")
349 |> post("/users/#{user.nickname}/outbox", data)
350
351 assert json_response(conn, 400)
352 end
353
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"])
357
358 data = %{
359 type: "Like",
360 object: %{
361 id: note_activity.data["object"]["id"]
362 }
363 }
364
365 conn =
366 conn
367 |> assign(:user, user)
368 |> put_req_header("content-type", "application/activity+json")
369 |> post("/users/#{user.nickname}/outbox", data)
370
371 result = json_response(conn, 201)
372 assert Activity.get_by_ap_id(result["id"])
373
374 object = Object.get_by_ap_id(note_activity.data["object"]["id"])
375 assert object
376 assert object.data["like_count"] == 1
377 end
378 end
379
380 describe "/users/:nickname/followers" do
381 test "it returns the followers in a collection", %{conn: conn} do
382 user = insert(:user)
383 user_two = insert(:user)
384 User.follow(user, user_two)
385
386 result =
387 conn
388 |> get("/users/#{user_two.nickname}/followers")
389 |> json_response(200)
390
391 assert result["first"]["orderedItems"] == [user.ap_id]
392 end
393
394 test "it returns returns empty if the user has 'hide_followers' set", %{conn: conn} do
395 user = insert(:user)
396 user_two = insert(:user, %{info: %{hide_followers: true}})
397 User.follow(user, user_two)
398
399 result =
400 conn
401 |> get("/users/#{user_two.nickname}/followers")
402 |> json_response(200)
403
404 assert result["first"]["orderedItems"] == []
405 assert result["totalItems"] == 0
406 end
407
408 test "it works for more than 10 users", %{conn: conn} do
409 user = insert(:user)
410
411 Enum.each(1..15, fn _ ->
412 other_user = insert(:user)
413 User.follow(other_user, user)
414 end)
415
416 result =
417 conn
418 |> get("/users/#{user.nickname}/followers")
419 |> json_response(200)
420
421 assert length(result["first"]["orderedItems"]) == 10
422 assert result["first"]["totalItems"] == 15
423 assert result["totalItems"] == 15
424
425 result =
426 conn
427 |> get("/users/#{user.nickname}/followers?page=2")
428 |> json_response(200)
429
430 assert length(result["orderedItems"]) == 5
431 assert result["totalItems"] == 15
432 end
433 end
434
435 describe "/users/:nickname/following" do
436 test "it returns the following in a collection", %{conn: conn} do
437 user = insert(:user)
438 user_two = insert(:user)
439 User.follow(user, user_two)
440
441 result =
442 conn
443 |> get("/users/#{user.nickname}/following")
444 |> json_response(200)
445
446 assert result["first"]["orderedItems"] == [user_two.ap_id]
447 end
448
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)
453
454 result =
455 conn
456 |> get("/users/#{user.nickname}/following")
457 |> json_response(200)
458
459 assert result["first"]["orderedItems"] == []
460 assert result["totalItems"] == 0
461 end
462
463 test "it works for more than 10 users", %{conn: conn} do
464 user = insert(:user)
465
466 Enum.each(1..15, fn _ ->
467 user = Repo.get(User, user.id)
468 other_user = insert(:user)
469 User.follow(user, other_user)
470 end)
471
472 result =
473 conn
474 |> get("/users/#{user.nickname}/following")
475 |> json_response(200)
476
477 assert length(result["first"]["orderedItems"]) == 10
478 assert result["first"]["totalItems"] == 15
479 assert result["totalItems"] == 15
480
481 result =
482 conn
483 |> get("/users/#{user.nickname}/following?page=2")
484 |> json_response(200)
485
486 assert length(result["orderedItems"]) == 5
487 assert result["totalItems"] == 15
488 end
489 end
490 end