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