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