1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do
6 use Pleroma.Web.ConnCase
8 alias Pleroma.Notification
11 alias Pleroma.Web.CommonAPI
13 import Pleroma.Factory
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)
19 {:ok, activity} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
20 {:ok, [_notification]} = Notification.create_notifications(activity)
24 |> assign(:user, user)
25 |> get("/api/v1/notifications")
26 |> json_response_and_validate_schema(200)
28 assert Enum.all?(response, fn n ->
29 get_in(n, ["account", "pleroma", "relationship"]) == %{}
33 test "list of notifications" do
34 %{user: user, conn: conn} = oauth_access(["read:notifications"])
35 other_user = insert(:user)
37 {:ok, activity} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
39 {:ok, [_notification]} = Notification.create_notifications(activity)
43 |> assign(:user, user)
44 |> get("/api/v1/notifications")
47 "hi <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{user.id}\" href=\"#{
49 }\" rel=\"ugc\">@<span>#{user.nickname}</span></a></span>"
51 assert [%{"status" => %{"content" => response}} | _rest] =
52 json_response_and_validate_schema(conn, 200)
54 assert response == expected_response
57 test "by default, does not contain pleroma:chat_mention" do
58 %{user: user, conn: conn} = oauth_access(["read:notifications"])
59 other_user = insert(:user)
61 {:ok, _activity} = CommonAPI.post_chat_message(other_user, user, "hey")
65 |> get("/api/v1/notifications")
66 |> json_response_and_validate_schema(200)
72 |> get("/api/v1/notifications?include_types[]=pleroma:chat_mention")
73 |> json_response_and_validate_schema(200)
78 test "getting a single notification" do
79 %{user: user, conn: conn} = oauth_access(["read:notifications"])
80 other_user = insert(:user)
82 {:ok, activity} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
84 {:ok, [notification]} = Notification.create_notifications(activity)
86 conn = get(conn, "/api/v1/notifications/#{notification.id}")
89 "hi <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{user.id}\" href=\"#{
91 }\" rel=\"ugc\">@<span>#{user.nickname}</span></a></span>"
93 assert %{"status" => %{"content" => response}} = json_response_and_validate_schema(conn, 200)
94 assert response == expected_response
97 test "dismissing a single notification (deprecated endpoint)" do
98 %{user: user, conn: conn} = oauth_access(["write:notifications"])
99 other_user = insert(:user)
101 {:ok, activity} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
103 {:ok, [notification]} = Notification.create_notifications(activity)
107 |> assign(:user, user)
108 |> put_req_header("content-type", "application/json")
109 |> post("/api/v1/notifications/dismiss", %{"id" => to_string(notification.id)})
111 assert %{} = json_response_and_validate_schema(conn, 200)
114 test "dismissing a single notification" do
115 %{user: user, conn: conn} = oauth_access(["write:notifications"])
116 other_user = insert(:user)
118 {:ok, activity} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
120 {:ok, [notification]} = Notification.create_notifications(activity)
124 |> assign(:user, user)
125 |> post("/api/v1/notifications/#{notification.id}/dismiss")
127 assert %{} = json_response_and_validate_schema(conn, 200)
130 test "clearing all notifications" do
131 %{user: user, conn: conn} = oauth_access(["write:notifications", "read:notifications"])
132 other_user = insert(:user)
134 {:ok, activity} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
136 {:ok, [_notification]} = Notification.create_notifications(activity)
138 ret_conn = post(conn, "/api/v1/notifications/clear")
140 assert %{} = json_response_and_validate_schema(ret_conn, 200)
142 ret_conn = get(conn, "/api/v1/notifications")
144 assert all = json_response_and_validate_schema(ret_conn, 200)
148 test "paginates notifications using min_id, since_id, max_id, and limit" do
149 %{user: user, conn: conn} = oauth_access(["read:notifications"])
150 other_user = insert(:user)
152 {:ok, activity1} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
153 {:ok, activity2} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
154 {:ok, activity3} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
155 {:ok, activity4} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
157 notification1_id = get_notification_id_by_activity(activity1)
158 notification2_id = get_notification_id_by_activity(activity2)
159 notification3_id = get_notification_id_by_activity(activity3)
160 notification4_id = get_notification_id_by_activity(activity4)
162 conn = assign(conn, :user, user)
167 |> get("/api/v1/notifications?limit=2&min_id=#{notification1_id}")
168 |> json_response_and_validate_schema(:ok)
170 assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
175 |> get("/api/v1/notifications?limit=2&since_id=#{notification1_id}")
176 |> json_response_and_validate_schema(:ok)
178 assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
183 |> get("/api/v1/notifications?limit=2&max_id=#{notification4_id}")
184 |> json_response_and_validate_schema(:ok)
186 assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
189 describe "exclude_visibilities" do
190 test "filters notifications for mentions" do
191 %{user: user, conn: conn} = oauth_access(["read:notifications"])
192 other_user = insert(:user)
194 {:ok, public_activity} =
195 CommonAPI.post(other_user, %{status: "@#{user.nickname}", visibility: "public"})
197 {:ok, direct_activity} =
198 CommonAPI.post(other_user, %{status: "@#{user.nickname}", visibility: "direct"})
200 {:ok, unlisted_activity} =
201 CommonAPI.post(other_user, %{status: "@#{user.nickname}", visibility: "unlisted"})
203 {:ok, private_activity} =
204 CommonAPI.post(other_user, %{status: "@#{user.nickname}", visibility: "private"})
206 query = params_to_query(%{exclude_visibilities: ["public", "unlisted", "private"]})
207 conn_res = get(conn, "/api/v1/notifications?" <> query)
209 assert [%{"status" => %{"id" => id}}] = json_response_and_validate_schema(conn_res, 200)
210 assert id == direct_activity.id
212 query = params_to_query(%{exclude_visibilities: ["public", "unlisted", "direct"]})
213 conn_res = get(conn, "/api/v1/notifications?" <> query)
215 assert [%{"status" => %{"id" => id}}] = json_response_and_validate_schema(conn_res, 200)
216 assert id == private_activity.id
218 query = params_to_query(%{exclude_visibilities: ["public", "private", "direct"]})
219 conn_res = get(conn, "/api/v1/notifications?" <> query)
221 assert [%{"status" => %{"id" => id}}] = json_response_and_validate_schema(conn_res, 200)
222 assert id == unlisted_activity.id
224 query = params_to_query(%{exclude_visibilities: ["unlisted", "private", "direct"]})
225 conn_res = get(conn, "/api/v1/notifications?" <> query)
227 assert [%{"status" => %{"id" => id}}] = json_response_and_validate_schema(conn_res, 200)
228 assert id == public_activity.id
231 test "filters notifications for Like activities" do
233 %{user: other_user, conn: conn} = oauth_access(["read:notifications"])
235 {:ok, public_activity} = CommonAPI.post(other_user, %{status: ".", visibility: "public"})
237 {:ok, direct_activity} =
238 CommonAPI.post(other_user, %{status: "@#{user.nickname}", visibility: "direct"})
240 {:ok, unlisted_activity} =
241 CommonAPI.post(other_user, %{status: ".", visibility: "unlisted"})
243 {:ok, private_activity} = CommonAPI.post(other_user, %{status: ".", visibility: "private"})
245 {:ok, _} = CommonAPI.favorite(user, public_activity.id)
246 {:ok, _} = CommonAPI.favorite(user, direct_activity.id)
247 {:ok, _} = CommonAPI.favorite(user, unlisted_activity.id)
248 {:ok, _} = CommonAPI.favorite(user, private_activity.id)
252 |> get("/api/v1/notifications?exclude_visibilities[]=direct")
253 |> json_response_and_validate_schema(200)
254 |> Enum.map(& &1["status"]["id"])
256 assert public_activity.id in activity_ids
257 assert unlisted_activity.id in activity_ids
258 assert private_activity.id in activity_ids
259 refute direct_activity.id in activity_ids
263 |> get("/api/v1/notifications?exclude_visibilities[]=unlisted")
264 |> json_response_and_validate_schema(200)
265 |> Enum.map(& &1["status"]["id"])
267 assert public_activity.id in activity_ids
268 refute unlisted_activity.id in activity_ids
269 assert private_activity.id in activity_ids
270 assert direct_activity.id in activity_ids
274 |> get("/api/v1/notifications?exclude_visibilities[]=private")
275 |> json_response_and_validate_schema(200)
276 |> Enum.map(& &1["status"]["id"])
278 assert public_activity.id in activity_ids
279 assert unlisted_activity.id in activity_ids
280 refute private_activity.id in activity_ids
281 assert direct_activity.id in activity_ids
285 |> get("/api/v1/notifications?exclude_visibilities[]=public")
286 |> json_response_and_validate_schema(200)
287 |> Enum.map(& &1["status"]["id"])
289 refute public_activity.id in activity_ids
290 assert unlisted_activity.id in activity_ids
291 assert private_activity.id in activity_ids
292 assert direct_activity.id in activity_ids
295 test "filters notifications for Announce activities" do
297 %{user: other_user, conn: conn} = oauth_access(["read:notifications"])
299 {:ok, public_activity} = CommonAPI.post(other_user, %{status: ".", visibility: "public"})
301 {:ok, unlisted_activity} =
302 CommonAPI.post(other_user, %{status: ".", visibility: "unlisted"})
304 {:ok, _} = CommonAPI.repeat(public_activity.id, user)
305 {:ok, _} = CommonAPI.repeat(unlisted_activity.id, user)
309 |> get("/api/v1/notifications?exclude_visibilities[]=unlisted")
310 |> json_response_and_validate_schema(200)
311 |> Enum.map(& &1["status"]["id"])
313 assert public_activity.id in activity_ids
314 refute unlisted_activity.id in activity_ids
318 test "filters notifications using exclude_types" do
319 %{user: user, conn: conn} = oauth_access(["read:notifications"])
320 other_user = insert(:user)
322 {:ok, mention_activity} = CommonAPI.post(other_user, %{status: "hey @#{user.nickname}"})
323 {:ok, create_activity} = CommonAPI.post(user, %{status: "hey"})
324 {:ok, favorite_activity} = CommonAPI.favorite(other_user, create_activity.id)
325 {:ok, reblog_activity} = CommonAPI.repeat(create_activity.id, other_user)
326 {:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user)
328 mention_notification_id = get_notification_id_by_activity(mention_activity)
329 favorite_notification_id = get_notification_id_by_activity(favorite_activity)
330 reblog_notification_id = get_notification_id_by_activity(reblog_activity)
331 follow_notification_id = get_notification_id_by_activity(follow_activity)
333 query = params_to_query(%{exclude_types: ["mention", "favourite", "reblog"]})
334 conn_res = get(conn, "/api/v1/notifications?" <> query)
336 assert [%{"id" => ^follow_notification_id}] = json_response_and_validate_schema(conn_res, 200)
338 query = params_to_query(%{exclude_types: ["favourite", "reblog", "follow"]})
339 conn_res = get(conn, "/api/v1/notifications?" <> query)
341 assert [%{"id" => ^mention_notification_id}] =
342 json_response_and_validate_schema(conn_res, 200)
344 query = params_to_query(%{exclude_types: ["reblog", "follow", "mention"]})
345 conn_res = get(conn, "/api/v1/notifications?" <> query)
347 assert [%{"id" => ^favorite_notification_id}] =
348 json_response_and_validate_schema(conn_res, 200)
350 query = params_to_query(%{exclude_types: ["follow", "mention", "favourite"]})
351 conn_res = get(conn, "/api/v1/notifications?" <> query)
353 assert [%{"id" => ^reblog_notification_id}] = json_response_and_validate_schema(conn_res, 200)
356 test "filters notifications using include_types" do
357 %{user: user, conn: conn} = oauth_access(["read:notifications"])
358 other_user = insert(:user)
360 {:ok, mention_activity} = CommonAPI.post(other_user, %{status: "hey @#{user.nickname}"})
361 {:ok, create_activity} = CommonAPI.post(user, %{status: "hey"})
362 {:ok, favorite_activity} = CommonAPI.favorite(other_user, create_activity.id)
363 {:ok, reblog_activity} = CommonAPI.repeat(create_activity.id, other_user)
364 {:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user)
366 mention_notification_id = get_notification_id_by_activity(mention_activity)
367 favorite_notification_id = get_notification_id_by_activity(favorite_activity)
368 reblog_notification_id = get_notification_id_by_activity(reblog_activity)
369 follow_notification_id = get_notification_id_by_activity(follow_activity)
371 conn_res = get(conn, "/api/v1/notifications?include_types[]=follow")
373 assert [%{"id" => ^follow_notification_id}] = json_response_and_validate_schema(conn_res, 200)
375 conn_res = get(conn, "/api/v1/notifications?include_types[]=mention")
377 assert [%{"id" => ^mention_notification_id}] =
378 json_response_and_validate_schema(conn_res, 200)
380 conn_res = get(conn, "/api/v1/notifications?include_types[]=favourite")
382 assert [%{"id" => ^favorite_notification_id}] =
383 json_response_and_validate_schema(conn_res, 200)
385 conn_res = get(conn, "/api/v1/notifications?include_types[]=reblog")
387 assert [%{"id" => ^reblog_notification_id}] = json_response_and_validate_schema(conn_res, 200)
389 result = conn |> get("/api/v1/notifications") |> json_response_and_validate_schema(200)
391 assert length(result) == 4
393 query = params_to_query(%{include_types: ["follow", "mention", "favourite", "reblog"]})
397 |> get("/api/v1/notifications?" <> query)
398 |> json_response_and_validate_schema(200)
400 assert length(result) == 4
403 test "destroy multiple" do
404 %{user: user, conn: conn} = oauth_access(["read:notifications", "write:notifications"])
405 other_user = insert(:user)
407 {:ok, activity1} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
408 {:ok, activity2} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
409 {:ok, activity3} = CommonAPI.post(user, %{status: "hi @#{other_user.nickname}"})
410 {:ok, activity4} = CommonAPI.post(user, %{status: "hi @#{other_user.nickname}"})
412 notification1_id = get_notification_id_by_activity(activity1)
413 notification2_id = get_notification_id_by_activity(activity2)
414 notification3_id = get_notification_id_by_activity(activity3)
415 notification4_id = get_notification_id_by_activity(activity4)
419 |> get("/api/v1/notifications")
420 |> json_response_and_validate_schema(:ok)
422 assert [%{"id" => ^notification2_id}, %{"id" => ^notification1_id}] = result
426 |> assign(:user, other_user)
427 |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:notifications"]))
431 |> get("/api/v1/notifications")
432 |> json_response_and_validate_schema(:ok)
434 assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
436 query = params_to_query(%{ids: [notification1_id, notification2_id]})
437 conn_destroy = delete(conn, "/api/v1/notifications/destroy_multiple?" <> query)
439 assert json_response_and_validate_schema(conn_destroy, 200) == %{}
443 |> get("/api/v1/notifications")
444 |> json_response_and_validate_schema(:ok)
446 assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
449 test "doesn't see notifications after muting user with notifications" do
450 %{user: user, conn: conn} = oauth_access(["read:notifications"])
451 user2 = insert(:user)
453 {:ok, _, _, _} = CommonAPI.follow(user, user2)
454 {:ok, _} = CommonAPI.post(user2, %{status: "hey @#{user.nickname}"})
456 ret_conn = get(conn, "/api/v1/notifications")
458 assert length(json_response_and_validate_schema(ret_conn, 200)) == 1
460 {:ok, _user_relationships} = User.mute(user, user2)
462 conn = get(conn, "/api/v1/notifications")
464 assert json_response_and_validate_schema(conn, 200) == []
467 test "see notifications after muting user without notifications" do
468 %{user: user, conn: conn} = oauth_access(["read:notifications"])
469 user2 = insert(:user)
471 {:ok, _, _, _} = CommonAPI.follow(user, user2)
472 {:ok, _} = CommonAPI.post(user2, %{status: "hey @#{user.nickname}"})
474 ret_conn = get(conn, "/api/v1/notifications")
476 assert length(json_response_and_validate_schema(ret_conn, 200)) == 1
478 {:ok, _user_relationships} = User.mute(user, user2, false)
480 conn = get(conn, "/api/v1/notifications")
482 assert length(json_response_and_validate_schema(conn, 200)) == 1
485 test "see notifications after muting user with notifications and with_muted parameter" do
486 %{user: user, conn: conn} = oauth_access(["read:notifications"])
487 user2 = insert(:user)
489 {:ok, _, _, _} = CommonAPI.follow(user, user2)
490 {:ok, _} = CommonAPI.post(user2, %{status: "hey @#{user.nickname}"})
492 ret_conn = get(conn, "/api/v1/notifications")
494 assert length(json_response_and_validate_schema(ret_conn, 200)) == 1
496 {:ok, _user_relationships} = User.mute(user, user2)
498 conn = get(conn, "/api/v1/notifications?with_muted=true")
500 assert length(json_response_and_validate_schema(conn, 200)) == 1
503 @tag capture_log: true
504 test "see move notifications" do
505 old_user = insert(:user)
506 new_user = insert(:user, also_known_as: [old_user.ap_id])
507 %{user: follower, conn: conn} = oauth_access(["read:notifications"])
509 old_user_url = old_user.ap_id
512 File.read!("test/fixtures/users_mock/localhost.json")
513 |> String.replace("{{nickname}}", old_user.nickname)
517 %{method: :get, url: ^old_user_url} ->
518 %Tesla.Env{status: 200, body: body}
521 User.follow(follower, old_user)
522 Pleroma.Web.ActivityPub.ActivityPub.move(old_user, new_user)
523 Pleroma.Tests.ObanHelpers.perform_all()
525 conn = get(conn, "/api/v1/notifications")
527 assert length(json_response_and_validate_schema(conn, 200)) == 1
530 describe "link headers" do
531 test "preserves parameters in link headers" do
532 %{user: user, conn: conn} = oauth_access(["read:notifications"])
533 other_user = insert(:user)
536 CommonAPI.post(other_user, %{
537 status: "hi @#{user.nickname}",
542 CommonAPI.post(other_user, %{
543 status: "hi @#{user.nickname}",
547 notification1 = Repo.get_by(Notification, activity_id: activity1.id)
548 notification2 = Repo.get_by(Notification, activity_id: activity2.id)
552 |> assign(:user, user)
553 |> get("/api/v1/notifications?limit=5")
555 assert [link_header] = get_resp_header(conn, "link")
556 assert link_header =~ ~r/limit=5/
557 assert link_header =~ ~r/min_id=#{notification2.id}/
558 assert link_header =~ ~r/max_id=#{notification1.id}/
562 describe "from specified user" do
564 %{user: user, conn: conn} = oauth_access(["read:notifications"])
566 %{id: account_id} = other_user1 = insert(:user)
567 other_user2 = insert(:user)
569 {:ok, _activity} = CommonAPI.post(other_user1, %{status: "hi @#{user.nickname}"})
570 {:ok, _activity} = CommonAPI.post(other_user2, %{status: "bye @#{user.nickname}"})
572 assert [%{"account" => %{"id" => ^account_id}}] =
574 |> assign(:user, user)
575 |> get("/api/v1/notifications?account_id=#{account_id}")
576 |> json_response_and_validate_schema(200)
578 assert %{"error" => "Account is not found"} =
580 |> assign(:user, user)
581 |> get("/api/v1/notifications?account_id=cofe")
582 |> json_response_and_validate_schema(404)
586 defp get_notification_id_by_activity(%{id: id}) do
588 |> Repo.get_by(activity_id: id)
593 defp params_to_query(%{} = params) do
594 Enum.map_join(params, "&", fn
595 {k, v} when is_list(v) -> Enum.map_join(v, "&", &"#{k}[]=#{&1}")
596 {k, v} -> k <> "=" <> v