Restricted embedding of relationships where applicable (statuses / notifications...
[akkoma] / test / web / mastodon_api / controllers / notification_controller_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do
6 use Pleroma.Web.ConnCase
7
8 alias Pleroma.Notification
9 alias Pleroma.Repo
10 alias Pleroma.User
11 alias Pleroma.Web.CommonAPI
12
13 import Pleroma.Factory
14
15 test "does NOT render account/pleroma/relationship by default" do
16 %{user: user, conn: conn} = oauth_access(["read:notifications"])
17 other_user = insert(:user)
18
19 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
20 {:ok, [_notification]} = Notification.create_notifications(activity)
21
22 response =
23 conn
24 |> assign(:user, user)
25 |> get("/api/v1/notifications")
26 |> json_response_and_validate_schema(200)
27
28 assert Enum.all?(response, fn n ->
29 get_in(n, ["account", "pleroma", "relationship"]) == %{}
30 end)
31 end
32
33 test "list of notifications" do
34 %{user: user, conn: conn} = oauth_access(["read:notifications"])
35 other_user = insert(:user)
36
37 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
38
39 {:ok, [_notification]} = Notification.create_notifications(activity)
40
41 conn =
42 conn
43 |> assign(:user, user)
44 |> get("/api/v1/notifications")
45
46 expected_response =
47 "hi <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{user.id}\" href=\"#{
48 user.ap_id
49 }\" rel=\"ugc\">@<span>#{user.nickname}</span></a></span>"
50
51 assert [%{"status" => %{"content" => response}} | _rest] =
52 json_response_and_validate_schema(conn, 200)
53
54 assert response == expected_response
55 end
56
57 test "getting a single notification" do
58 %{user: user, conn: conn} = oauth_access(["read:notifications"])
59 other_user = insert(:user)
60
61 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
62
63 {:ok, [notification]} = Notification.create_notifications(activity)
64
65 conn = get(conn, "/api/v1/notifications/#{notification.id}")
66
67 expected_response =
68 "hi <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{user.id}\" href=\"#{
69 user.ap_id
70 }\" rel=\"ugc\">@<span>#{user.nickname}</span></a></span>"
71
72 assert %{"status" => %{"content" => response}} = json_response_and_validate_schema(conn, 200)
73 assert response == expected_response
74 end
75
76 test "dismissing a single notification (deprecated endpoint)" do
77 %{user: user, conn: conn} = oauth_access(["write:notifications"])
78 other_user = insert(:user)
79
80 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
81
82 {:ok, [notification]} = Notification.create_notifications(activity)
83
84 conn =
85 conn
86 |> assign(:user, user)
87 |> put_req_header("content-type", "application/json")
88 |> post("/api/v1/notifications/dismiss", %{"id" => to_string(notification.id)})
89
90 assert %{} = json_response_and_validate_schema(conn, 200)
91 end
92
93 test "dismissing a single notification" do
94 %{user: user, conn: conn} = oauth_access(["write:notifications"])
95 other_user = insert(:user)
96
97 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
98
99 {:ok, [notification]} = Notification.create_notifications(activity)
100
101 conn =
102 conn
103 |> assign(:user, user)
104 |> post("/api/v1/notifications/#{notification.id}/dismiss")
105
106 assert %{} = json_response_and_validate_schema(conn, 200)
107 end
108
109 test "clearing all notifications" do
110 %{user: user, conn: conn} = oauth_access(["write:notifications", "read:notifications"])
111 other_user = insert(:user)
112
113 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
114
115 {:ok, [_notification]} = Notification.create_notifications(activity)
116
117 ret_conn = post(conn, "/api/v1/notifications/clear")
118
119 assert %{} = json_response_and_validate_schema(ret_conn, 200)
120
121 ret_conn = get(conn, "/api/v1/notifications")
122
123 assert all = json_response_and_validate_schema(ret_conn, 200)
124 assert all == []
125 end
126
127 test "paginates notifications using min_id, since_id, max_id, and limit" do
128 %{user: user, conn: conn} = oauth_access(["read:notifications"])
129 other_user = insert(:user)
130
131 {:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
132 {:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
133 {:ok, activity3} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
134 {:ok, activity4} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
135
136 notification1_id = get_notification_id_by_activity(activity1)
137 notification2_id = get_notification_id_by_activity(activity2)
138 notification3_id = get_notification_id_by_activity(activity3)
139 notification4_id = get_notification_id_by_activity(activity4)
140
141 conn = assign(conn, :user, user)
142
143 # min_id
144 result =
145 conn
146 |> get("/api/v1/notifications?limit=2&min_id=#{notification1_id}")
147 |> json_response_and_validate_schema(:ok)
148
149 assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
150
151 # since_id
152 result =
153 conn
154 |> get("/api/v1/notifications?limit=2&since_id=#{notification1_id}")
155 |> json_response_and_validate_schema(:ok)
156
157 assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
158
159 # max_id
160 result =
161 conn
162 |> get("/api/v1/notifications?limit=2&max_id=#{notification4_id}")
163 |> json_response_and_validate_schema(:ok)
164
165 assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
166 end
167
168 describe "exclude_visibilities" do
169 test "filters notifications for mentions" do
170 %{user: user, conn: conn} = oauth_access(["read:notifications"])
171 other_user = insert(:user)
172
173 {:ok, public_activity} =
174 CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "public"})
175
176 {:ok, direct_activity} =
177 CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "direct"})
178
179 {:ok, unlisted_activity} =
180 CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "unlisted"})
181
182 {:ok, private_activity} =
183 CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "private"})
184
185 query = params_to_query(%{exclude_visibilities: ["public", "unlisted", "private"]})
186 conn_res = get(conn, "/api/v1/notifications?" <> query)
187
188 assert [%{"status" => %{"id" => id}}] = json_response_and_validate_schema(conn_res, 200)
189 assert id == direct_activity.id
190
191 query = params_to_query(%{exclude_visibilities: ["public", "unlisted", "direct"]})
192 conn_res = get(conn, "/api/v1/notifications?" <> query)
193
194 assert [%{"status" => %{"id" => id}}] = json_response_and_validate_schema(conn_res, 200)
195 assert id == private_activity.id
196
197 query = params_to_query(%{exclude_visibilities: ["public", "private", "direct"]})
198 conn_res = get(conn, "/api/v1/notifications?" <> query)
199
200 assert [%{"status" => %{"id" => id}}] = json_response_and_validate_schema(conn_res, 200)
201 assert id == unlisted_activity.id
202
203 query = params_to_query(%{exclude_visibilities: ["unlisted", "private", "direct"]})
204 conn_res = get(conn, "/api/v1/notifications?" <> query)
205
206 assert [%{"status" => %{"id" => id}}] = json_response_and_validate_schema(conn_res, 200)
207 assert id == public_activity.id
208 end
209
210 test "filters notifications for Like activities" do
211 user = insert(:user)
212 %{user: other_user, conn: conn} = oauth_access(["read:notifications"])
213
214 {:ok, public_activity} =
215 CommonAPI.post(other_user, %{"status" => ".", "visibility" => "public"})
216
217 {:ok, direct_activity} =
218 CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "direct"})
219
220 {:ok, unlisted_activity} =
221 CommonAPI.post(other_user, %{"status" => ".", "visibility" => "unlisted"})
222
223 {:ok, private_activity} =
224 CommonAPI.post(other_user, %{"status" => ".", "visibility" => "private"})
225
226 {:ok, _} = CommonAPI.favorite(user, public_activity.id)
227 {:ok, _} = CommonAPI.favorite(user, direct_activity.id)
228 {:ok, _} = CommonAPI.favorite(user, unlisted_activity.id)
229 {:ok, _} = CommonAPI.favorite(user, private_activity.id)
230
231 activity_ids =
232 conn
233 |> get("/api/v1/notifications?exclude_visibilities[]=direct")
234 |> json_response_and_validate_schema(200)
235 |> Enum.map(& &1["status"]["id"])
236
237 assert public_activity.id in activity_ids
238 assert unlisted_activity.id in activity_ids
239 assert private_activity.id in activity_ids
240 refute direct_activity.id in activity_ids
241
242 activity_ids =
243 conn
244 |> get("/api/v1/notifications?exclude_visibilities[]=unlisted")
245 |> json_response_and_validate_schema(200)
246 |> Enum.map(& &1["status"]["id"])
247
248 assert public_activity.id in activity_ids
249 refute unlisted_activity.id in activity_ids
250 assert private_activity.id in activity_ids
251 assert direct_activity.id in activity_ids
252
253 activity_ids =
254 conn
255 |> get("/api/v1/notifications?exclude_visibilities[]=private")
256 |> json_response_and_validate_schema(200)
257 |> Enum.map(& &1["status"]["id"])
258
259 assert public_activity.id in activity_ids
260 assert unlisted_activity.id in activity_ids
261 refute private_activity.id in activity_ids
262 assert direct_activity.id in activity_ids
263
264 activity_ids =
265 conn
266 |> get("/api/v1/notifications?exclude_visibilities[]=public")
267 |> json_response_and_validate_schema(200)
268 |> Enum.map(& &1["status"]["id"])
269
270 refute public_activity.id in activity_ids
271 assert unlisted_activity.id in activity_ids
272 assert private_activity.id in activity_ids
273 assert direct_activity.id in activity_ids
274 end
275
276 test "filters notifications for Announce activities" do
277 user = insert(:user)
278 %{user: other_user, conn: conn} = oauth_access(["read:notifications"])
279
280 {:ok, public_activity} =
281 CommonAPI.post(other_user, %{"status" => ".", "visibility" => "public"})
282
283 {:ok, unlisted_activity} =
284 CommonAPI.post(other_user, %{"status" => ".", "visibility" => "unlisted"})
285
286 {:ok, _, _} = CommonAPI.repeat(public_activity.id, user)
287 {:ok, _, _} = CommonAPI.repeat(unlisted_activity.id, user)
288
289 activity_ids =
290 conn
291 |> get("/api/v1/notifications?exclude_visibilities[]=unlisted")
292 |> json_response_and_validate_schema(200)
293 |> Enum.map(& &1["status"]["id"])
294
295 assert public_activity.id in activity_ids
296 refute unlisted_activity.id in activity_ids
297 end
298 end
299
300 test "filters notifications using exclude_types" do
301 %{user: user, conn: conn} = oauth_access(["read:notifications"])
302 other_user = insert(:user)
303
304 {:ok, mention_activity} = CommonAPI.post(other_user, %{"status" => "hey @#{user.nickname}"})
305 {:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"})
306 {:ok, favorite_activity} = CommonAPI.favorite(other_user, create_activity.id)
307 {:ok, reblog_activity, _} = CommonAPI.repeat(create_activity.id, other_user)
308 {:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user)
309
310 mention_notification_id = get_notification_id_by_activity(mention_activity)
311 favorite_notification_id = get_notification_id_by_activity(favorite_activity)
312 reblog_notification_id = get_notification_id_by_activity(reblog_activity)
313 follow_notification_id = get_notification_id_by_activity(follow_activity)
314
315 query = params_to_query(%{exclude_types: ["mention", "favourite", "reblog"]})
316 conn_res = get(conn, "/api/v1/notifications?" <> query)
317
318 assert [%{"id" => ^follow_notification_id}] = json_response_and_validate_schema(conn_res, 200)
319
320 query = params_to_query(%{exclude_types: ["favourite", "reblog", "follow"]})
321 conn_res = get(conn, "/api/v1/notifications?" <> query)
322
323 assert [%{"id" => ^mention_notification_id}] =
324 json_response_and_validate_schema(conn_res, 200)
325
326 query = params_to_query(%{exclude_types: ["reblog", "follow", "mention"]})
327 conn_res = get(conn, "/api/v1/notifications?" <> query)
328
329 assert [%{"id" => ^favorite_notification_id}] =
330 json_response_and_validate_schema(conn_res, 200)
331
332 query = params_to_query(%{exclude_types: ["follow", "mention", "favourite"]})
333 conn_res = get(conn, "/api/v1/notifications?" <> query)
334
335 assert [%{"id" => ^reblog_notification_id}] = json_response_and_validate_schema(conn_res, 200)
336 end
337
338 test "filters notifications using include_types" do
339 %{user: user, conn: conn} = oauth_access(["read:notifications"])
340 other_user = insert(:user)
341
342 {:ok, mention_activity} = CommonAPI.post(other_user, %{"status" => "hey @#{user.nickname}"})
343 {:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"})
344 {:ok, favorite_activity} = CommonAPI.favorite(other_user, create_activity.id)
345 {:ok, reblog_activity, _} = CommonAPI.repeat(create_activity.id, other_user)
346 {:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user)
347
348 mention_notification_id = get_notification_id_by_activity(mention_activity)
349 favorite_notification_id = get_notification_id_by_activity(favorite_activity)
350 reblog_notification_id = get_notification_id_by_activity(reblog_activity)
351 follow_notification_id = get_notification_id_by_activity(follow_activity)
352
353 conn_res = get(conn, "/api/v1/notifications?include_types[]=follow")
354
355 assert [%{"id" => ^follow_notification_id}] = json_response_and_validate_schema(conn_res, 200)
356
357 conn_res = get(conn, "/api/v1/notifications?include_types[]=mention")
358
359 assert [%{"id" => ^mention_notification_id}] =
360 json_response_and_validate_schema(conn_res, 200)
361
362 conn_res = get(conn, "/api/v1/notifications?include_types[]=favourite")
363
364 assert [%{"id" => ^favorite_notification_id}] =
365 json_response_and_validate_schema(conn_res, 200)
366
367 conn_res = get(conn, "/api/v1/notifications?include_types[]=reblog")
368
369 assert [%{"id" => ^reblog_notification_id}] = json_response_and_validate_schema(conn_res, 200)
370
371 result = conn |> get("/api/v1/notifications") |> json_response_and_validate_schema(200)
372
373 assert length(result) == 4
374
375 query = params_to_query(%{include_types: ["follow", "mention", "favourite", "reblog"]})
376
377 result =
378 conn
379 |> get("/api/v1/notifications?" <> query)
380 |> json_response_and_validate_schema(200)
381
382 assert length(result) == 4
383 end
384
385 test "destroy multiple" do
386 %{user: user, conn: conn} = oauth_access(["read:notifications", "write:notifications"])
387 other_user = insert(:user)
388
389 {:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
390 {:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
391 {:ok, activity3} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"})
392 {:ok, activity4} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"})
393
394 notification1_id = get_notification_id_by_activity(activity1)
395 notification2_id = get_notification_id_by_activity(activity2)
396 notification3_id = get_notification_id_by_activity(activity3)
397 notification4_id = get_notification_id_by_activity(activity4)
398
399 result =
400 conn
401 |> get("/api/v1/notifications")
402 |> json_response_and_validate_schema(:ok)
403
404 assert [%{"id" => ^notification2_id}, %{"id" => ^notification1_id}] = result
405
406 conn2 =
407 conn
408 |> assign(:user, other_user)
409 |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:notifications"]))
410
411 result =
412 conn2
413 |> get("/api/v1/notifications")
414 |> json_response_and_validate_schema(:ok)
415
416 assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
417
418 query = params_to_query(%{ids: [notification1_id, notification2_id]})
419 conn_destroy = delete(conn, "/api/v1/notifications/destroy_multiple?" <> query)
420
421 assert json_response_and_validate_schema(conn_destroy, 200) == %{}
422
423 result =
424 conn2
425 |> get("/api/v1/notifications")
426 |> json_response_and_validate_schema(:ok)
427
428 assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
429 end
430
431 test "doesn't see notifications after muting user with notifications" do
432 %{user: user, conn: conn} = oauth_access(["read:notifications"])
433 user2 = insert(:user)
434
435 {:ok, _, _, _} = CommonAPI.follow(user, user2)
436 {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"})
437
438 ret_conn = get(conn, "/api/v1/notifications")
439
440 assert length(json_response_and_validate_schema(ret_conn, 200)) == 1
441
442 {:ok, _user_relationships} = User.mute(user, user2)
443
444 conn = get(conn, "/api/v1/notifications")
445
446 assert json_response_and_validate_schema(conn, 200) == []
447 end
448
449 test "see notifications after muting user without notifications" do
450 %{user: user, conn: conn} = oauth_access(["read:notifications"])
451 user2 = insert(:user)
452
453 {:ok, _, _, _} = CommonAPI.follow(user, user2)
454 {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"})
455
456 ret_conn = get(conn, "/api/v1/notifications")
457
458 assert length(json_response_and_validate_schema(ret_conn, 200)) == 1
459
460 {:ok, _user_relationships} = User.mute(user, user2, false)
461
462 conn = get(conn, "/api/v1/notifications")
463
464 assert length(json_response_and_validate_schema(conn, 200)) == 1
465 end
466
467 test "see notifications after muting user with notifications and with_muted parameter" do
468 %{user: user, conn: conn} = oauth_access(["read:notifications"])
469 user2 = insert(:user)
470
471 {:ok, _, _, _} = CommonAPI.follow(user, user2)
472 {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"})
473
474 ret_conn = get(conn, "/api/v1/notifications")
475
476 assert length(json_response_and_validate_schema(ret_conn, 200)) == 1
477
478 {:ok, _user_relationships} = User.mute(user, user2)
479
480 conn = get(conn, "/api/v1/notifications?with_muted=true")
481
482 assert length(json_response_and_validate_schema(conn, 200)) == 1
483 end
484
485 @tag capture_log: true
486 test "see move notifications" do
487 old_user = insert(:user)
488 new_user = insert(:user, also_known_as: [old_user.ap_id])
489 %{user: follower, conn: conn} = oauth_access(["read:notifications"])
490
491 old_user_url = old_user.ap_id
492
493 body =
494 File.read!("test/fixtures/users_mock/localhost.json")
495 |> String.replace("{{nickname}}", old_user.nickname)
496 |> Jason.encode!()
497
498 Tesla.Mock.mock(fn
499 %{method: :get, url: ^old_user_url} ->
500 %Tesla.Env{status: 200, body: body}
501 end)
502
503 User.follow(follower, old_user)
504 Pleroma.Web.ActivityPub.ActivityPub.move(old_user, new_user)
505 Pleroma.Tests.ObanHelpers.perform_all()
506
507 conn = get(conn, "/api/v1/notifications")
508
509 assert length(json_response_and_validate_schema(conn, 200)) == 1
510 end
511
512 describe "link headers" do
513 test "preserves parameters in link headers" do
514 %{user: user, conn: conn} = oauth_access(["read:notifications"])
515 other_user = insert(:user)
516
517 {:ok, activity1} =
518 CommonAPI.post(other_user, %{
519 "status" => "hi @#{user.nickname}",
520 "visibility" => "public"
521 })
522
523 {:ok, activity2} =
524 CommonAPI.post(other_user, %{
525 "status" => "hi @#{user.nickname}",
526 "visibility" => "public"
527 })
528
529 notification1 = Repo.get_by(Notification, activity_id: activity1.id)
530 notification2 = Repo.get_by(Notification, activity_id: activity2.id)
531
532 conn =
533 conn
534 |> assign(:user, user)
535 |> get("/api/v1/notifications?limit=5")
536
537 assert [link_header] = get_resp_header(conn, "link")
538 assert link_header =~ ~r/limit=5/
539 assert link_header =~ ~r/min_id=#{notification2.id}/
540 assert link_header =~ ~r/max_id=#{notification1.id}/
541 end
542 end
543
544 describe "from specified user" do
545 test "account_id" do
546 %{user: user, conn: conn} = oauth_access(["read:notifications"])
547
548 %{id: account_id} = other_user1 = insert(:user)
549 other_user2 = insert(:user)
550
551 {:ok, _activity} = CommonAPI.post(other_user1, %{"status" => "hi @#{user.nickname}"})
552 {:ok, _activity} = CommonAPI.post(other_user2, %{"status" => "bye @#{user.nickname}"})
553
554 assert [%{"account" => %{"id" => ^account_id}}] =
555 conn
556 |> assign(:user, user)
557 |> get("/api/v1/notifications?account_id=#{account_id}")
558 |> json_response_and_validate_schema(200)
559
560 assert %{"error" => "Account is not found"} =
561 conn
562 |> assign(:user, user)
563 |> get("/api/v1/notifications?account_id=cofe")
564 |> json_response_and_validate_schema(404)
565 end
566 end
567
568 defp get_notification_id_by_activity(%{id: id}) do
569 Notification
570 |> Repo.get_by(activity_id: id)
571 |> Map.get(:id)
572 |> to_string()
573 end
574
575 defp params_to_query(%{} = params) do
576 Enum.map_join(params, "&", fn
577 {k, v} when is_list(v) -> Enum.map_join(v, "&", &"#{k}[]=#{&1}")
578 {k, v} -> k <> "=" <> v
579 end)
580 end
581 end