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