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 if this is disabled by default" do
16 clear_config([:extensions, :output_relationships_in_statuses_by_default], false)
18 %{user: user, conn: conn} = oauth_access(["read:notifications"])
19 other_user = insert(:user)
21 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
22 {:ok, [_notification]} = Notification.create_notifications(activity)
26 |> assign(:user, user)
27 |> get("/api/v1/notifications")
28 |> json_response_and_validate_schema(200)
30 assert Enum.all?(response, fn n ->
31 get_in(n, ["account", "pleroma", "relationship"]) == %{}
35 test "list of notifications" do
36 %{user: user, conn: conn} = oauth_access(["read:notifications"])
37 other_user = insert(:user)
39 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
41 {:ok, [_notification]} = Notification.create_notifications(activity)
45 |> assign(:user, user)
46 |> get("/api/v1/notifications")
49 "hi <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{user.id}\" href=\"#{
51 }\" rel=\"ugc\">@<span>#{user.nickname}</span></a></span>"
53 assert [%{"status" => %{"content" => response}} | _rest] =
54 json_response_and_validate_schema(conn, 200)
56 assert response == expected_response
59 test "getting a single notification" do
60 %{user: user, conn: conn} = oauth_access(["read:notifications"])
61 other_user = insert(:user)
63 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
65 {:ok, [notification]} = Notification.create_notifications(activity)
67 conn = get(conn, "/api/v1/notifications/#{notification.id}")
70 "hi <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{user.id}\" href=\"#{
72 }\" rel=\"ugc\">@<span>#{user.nickname}</span></a></span>"
74 assert %{"status" => %{"content" => response}} = json_response_and_validate_schema(conn, 200)
75 assert response == expected_response
78 test "dismissing a single notification (deprecated endpoint)" do
79 %{user: user, conn: conn} = oauth_access(["write:notifications"])
80 other_user = insert(:user)
82 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
84 {:ok, [notification]} = Notification.create_notifications(activity)
88 |> assign(:user, user)
89 |> put_req_header("content-type", "application/json")
90 |> post("/api/v1/notifications/dismiss", %{"id" => to_string(notification.id)})
92 assert %{} = json_response_and_validate_schema(conn, 200)
95 test "dismissing a single notification" do
96 %{user: user, conn: conn} = oauth_access(["write:notifications"])
97 other_user = insert(:user)
99 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
101 {:ok, [notification]} = Notification.create_notifications(activity)
105 |> assign(:user, user)
106 |> post("/api/v1/notifications/#{notification.id}/dismiss")
108 assert %{} = json_response_and_validate_schema(conn, 200)
111 test "clearing all notifications" do
112 %{user: user, conn: conn} = oauth_access(["write:notifications", "read:notifications"])
113 other_user = insert(:user)
115 {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
117 {:ok, [_notification]} = Notification.create_notifications(activity)
119 ret_conn = post(conn, "/api/v1/notifications/clear")
121 assert %{} = json_response_and_validate_schema(ret_conn, 200)
123 ret_conn = get(conn, "/api/v1/notifications")
125 assert all = json_response_and_validate_schema(ret_conn, 200)
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)
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}"})
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)
143 conn = assign(conn, :user, user)
148 |> get("/api/v1/notifications?limit=2&min_id=#{notification1_id}")
149 |> json_response_and_validate_schema(:ok)
151 assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
156 |> get("/api/v1/notifications?limit=2&since_id=#{notification1_id}")
157 |> json_response_and_validate_schema(:ok)
159 assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
164 |> get("/api/v1/notifications?limit=2&max_id=#{notification4_id}")
165 |> json_response_and_validate_schema(:ok)
167 assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
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)
175 {:ok, public_activity} =
176 CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "public"})
178 {:ok, direct_activity} =
179 CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "direct"})
181 {:ok, unlisted_activity} =
182 CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "unlisted"})
184 {:ok, private_activity} =
185 CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "private"})
187 query = params_to_query(%{exclude_visibilities: ["public", "unlisted", "private"]})
188 conn_res = get(conn, "/api/v1/notifications?" <> query)
190 assert [%{"status" => %{"id" => id}}] = json_response_and_validate_schema(conn_res, 200)
191 assert id == direct_activity.id
193 query = params_to_query(%{exclude_visibilities: ["public", "unlisted", "direct"]})
194 conn_res = get(conn, "/api/v1/notifications?" <> query)
196 assert [%{"status" => %{"id" => id}}] = json_response_and_validate_schema(conn_res, 200)
197 assert id == private_activity.id
199 query = params_to_query(%{exclude_visibilities: ["public", "private", "direct"]})
200 conn_res = get(conn, "/api/v1/notifications?" <> query)
202 assert [%{"status" => %{"id" => id}}] = json_response_and_validate_schema(conn_res, 200)
203 assert id == unlisted_activity.id
205 query = params_to_query(%{exclude_visibilities: ["unlisted", "private", "direct"]})
206 conn_res = get(conn, "/api/v1/notifications?" <> query)
208 assert [%{"status" => %{"id" => id}}] = json_response_and_validate_schema(conn_res, 200)
209 assert id == public_activity.id
212 test "filters notifications for Like activities" do
214 %{user: other_user, conn: conn} = oauth_access(["read:notifications"])
216 {:ok, public_activity} =
217 CommonAPI.post(other_user, %{"status" => ".", "visibility" => "public"})
219 {:ok, direct_activity} =
220 CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "direct"})
222 {:ok, unlisted_activity} =
223 CommonAPI.post(other_user, %{"status" => ".", "visibility" => "unlisted"})
225 {:ok, private_activity} =
226 CommonAPI.post(other_user, %{"status" => ".", "visibility" => "private"})
228 {:ok, _} = CommonAPI.favorite(user, public_activity.id)
229 {:ok, _} = CommonAPI.favorite(user, direct_activity.id)
230 {:ok, _} = CommonAPI.favorite(user, unlisted_activity.id)
231 {:ok, _} = CommonAPI.favorite(user, private_activity.id)
235 |> get("/api/v1/notifications?exclude_visibilities[]=direct")
236 |> json_response_and_validate_schema(200)
237 |> Enum.map(& &1["status"]["id"])
239 assert public_activity.id in activity_ids
240 assert unlisted_activity.id in activity_ids
241 assert private_activity.id in activity_ids
242 refute direct_activity.id in activity_ids
246 |> get("/api/v1/notifications?exclude_visibilities[]=unlisted")
247 |> json_response_and_validate_schema(200)
248 |> Enum.map(& &1["status"]["id"])
250 assert public_activity.id in activity_ids
251 refute unlisted_activity.id in activity_ids
252 assert private_activity.id in activity_ids
253 assert direct_activity.id in activity_ids
257 |> get("/api/v1/notifications?exclude_visibilities[]=private")
258 |> json_response_and_validate_schema(200)
259 |> Enum.map(& &1["status"]["id"])
261 assert public_activity.id in activity_ids
262 assert unlisted_activity.id in activity_ids
263 refute private_activity.id in activity_ids
264 assert direct_activity.id in activity_ids
268 |> get("/api/v1/notifications?exclude_visibilities[]=public")
269 |> json_response_and_validate_schema(200)
270 |> Enum.map(& &1["status"]["id"])
272 refute public_activity.id in activity_ids
273 assert unlisted_activity.id in activity_ids
274 assert private_activity.id in activity_ids
275 assert direct_activity.id in activity_ids
278 test "filters notifications for Announce activities" do
280 %{user: other_user, conn: conn} = oauth_access(["read:notifications"])
282 {:ok, public_activity} =
283 CommonAPI.post(other_user, %{"status" => ".", "visibility" => "public"})
285 {:ok, unlisted_activity} =
286 CommonAPI.post(other_user, %{"status" => ".", "visibility" => "unlisted"})
288 {:ok, _, _} = CommonAPI.repeat(public_activity.id, user)
289 {:ok, _, _} = CommonAPI.repeat(unlisted_activity.id, user)
293 |> get("/api/v1/notifications?exclude_visibilities[]=unlisted")
294 |> json_response_and_validate_schema(200)
295 |> Enum.map(& &1["status"]["id"])
297 assert public_activity.id in activity_ids
298 refute unlisted_activity.id in activity_ids
302 test "filters notifications using exclude_types" do
303 %{user: user, conn: conn} = oauth_access(["read:notifications"])
304 other_user = insert(:user)
306 {:ok, mention_activity} = CommonAPI.post(other_user, %{"status" => "hey @#{user.nickname}"})
307 {:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"})
308 {:ok, favorite_activity} = CommonAPI.favorite(other_user, create_activity.id)
309 {:ok, reblog_activity, _} = CommonAPI.repeat(create_activity.id, other_user)
310 {:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user)
312 mention_notification_id = get_notification_id_by_activity(mention_activity)
313 favorite_notification_id = get_notification_id_by_activity(favorite_activity)
314 reblog_notification_id = get_notification_id_by_activity(reblog_activity)
315 follow_notification_id = get_notification_id_by_activity(follow_activity)
317 query = params_to_query(%{exclude_types: ["mention", "favourite", "reblog"]})
318 conn_res = get(conn, "/api/v1/notifications?" <> query)
320 assert [%{"id" => ^follow_notification_id}] = json_response_and_validate_schema(conn_res, 200)
322 query = params_to_query(%{exclude_types: ["favourite", "reblog", "follow"]})
323 conn_res = get(conn, "/api/v1/notifications?" <> query)
325 assert [%{"id" => ^mention_notification_id}] =
326 json_response_and_validate_schema(conn_res, 200)
328 query = params_to_query(%{exclude_types: ["reblog", "follow", "mention"]})
329 conn_res = get(conn, "/api/v1/notifications?" <> query)
331 assert [%{"id" => ^favorite_notification_id}] =
332 json_response_and_validate_schema(conn_res, 200)
334 query = params_to_query(%{exclude_types: ["follow", "mention", "favourite"]})
335 conn_res = get(conn, "/api/v1/notifications?" <> query)
337 assert [%{"id" => ^reblog_notification_id}] = json_response_and_validate_schema(conn_res, 200)
340 test "filters notifications using include_types" do
341 %{user: user, conn: conn} = oauth_access(["read:notifications"])
342 other_user = insert(:user)
344 {:ok, mention_activity} = CommonAPI.post(other_user, %{"status" => "hey @#{user.nickname}"})
345 {:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"})
346 {:ok, favorite_activity} = CommonAPI.favorite(other_user, create_activity.id)
347 {:ok, reblog_activity, _} = CommonAPI.repeat(create_activity.id, other_user)
348 {:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user)
350 mention_notification_id = get_notification_id_by_activity(mention_activity)
351 favorite_notification_id = get_notification_id_by_activity(favorite_activity)
352 reblog_notification_id = get_notification_id_by_activity(reblog_activity)
353 follow_notification_id = get_notification_id_by_activity(follow_activity)
355 conn_res = get(conn, "/api/v1/notifications?include_types[]=follow")
357 assert [%{"id" => ^follow_notification_id}] = json_response_and_validate_schema(conn_res, 200)
359 conn_res = get(conn, "/api/v1/notifications?include_types[]=mention")
361 assert [%{"id" => ^mention_notification_id}] =
362 json_response_and_validate_schema(conn_res, 200)
364 conn_res = get(conn, "/api/v1/notifications?include_types[]=favourite")
366 assert [%{"id" => ^favorite_notification_id}] =
367 json_response_and_validate_schema(conn_res, 200)
369 conn_res = get(conn, "/api/v1/notifications?include_types[]=reblog")
371 assert [%{"id" => ^reblog_notification_id}] = json_response_and_validate_schema(conn_res, 200)
373 result = conn |> get("/api/v1/notifications") |> json_response_and_validate_schema(200)
375 assert length(result) == 4
377 query = params_to_query(%{include_types: ["follow", "mention", "favourite", "reblog"]})
381 |> get("/api/v1/notifications?" <> query)
382 |> json_response_and_validate_schema(200)
384 assert length(result) == 4
387 test "destroy multiple" do
388 %{user: user, conn: conn} = oauth_access(["read:notifications", "write:notifications"])
389 other_user = insert(:user)
391 {:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
392 {:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
393 {:ok, activity3} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"})
394 {:ok, activity4} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"})
396 notification1_id = get_notification_id_by_activity(activity1)
397 notification2_id = get_notification_id_by_activity(activity2)
398 notification3_id = get_notification_id_by_activity(activity3)
399 notification4_id = get_notification_id_by_activity(activity4)
403 |> get("/api/v1/notifications")
404 |> json_response_and_validate_schema(:ok)
406 assert [%{"id" => ^notification2_id}, %{"id" => ^notification1_id}] = result
410 |> assign(:user, other_user)
411 |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:notifications"]))
415 |> get("/api/v1/notifications")
416 |> json_response_and_validate_schema(:ok)
418 assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
420 query = params_to_query(%{ids: [notification1_id, notification2_id]})
421 conn_destroy = delete(conn, "/api/v1/notifications/destroy_multiple?" <> query)
423 assert json_response_and_validate_schema(conn_destroy, 200) == %{}
427 |> get("/api/v1/notifications")
428 |> json_response_and_validate_schema(:ok)
430 assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
433 test "doesn't see notifications after muting user with notifications" do
434 %{user: user, conn: conn} = oauth_access(["read:notifications"])
435 user2 = insert(:user)
437 {:ok, _, _, _} = CommonAPI.follow(user, user2)
438 {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"})
440 ret_conn = get(conn, "/api/v1/notifications")
442 assert length(json_response_and_validate_schema(ret_conn, 200)) == 1
444 {:ok, _user_relationships} = User.mute(user, user2)
446 conn = get(conn, "/api/v1/notifications")
448 assert json_response_and_validate_schema(conn, 200) == []
451 test "see notifications after muting user without notifications" do
452 %{user: user, conn: conn} = oauth_access(["read:notifications"])
453 user2 = insert(:user)
455 {:ok, _, _, _} = CommonAPI.follow(user, user2)
456 {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"})
458 ret_conn = get(conn, "/api/v1/notifications")
460 assert length(json_response_and_validate_schema(ret_conn, 200)) == 1
462 {:ok, _user_relationships} = User.mute(user, user2, false)
464 conn = get(conn, "/api/v1/notifications")
466 assert length(json_response_and_validate_schema(conn, 200)) == 1
469 test "see notifications after muting user with notifications and with_muted parameter" do
470 %{user: user, conn: conn} = oauth_access(["read:notifications"])
471 user2 = insert(:user)
473 {:ok, _, _, _} = CommonAPI.follow(user, user2)
474 {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"})
476 ret_conn = get(conn, "/api/v1/notifications")
478 assert length(json_response_and_validate_schema(ret_conn, 200)) == 1
480 {:ok, _user_relationships} = User.mute(user, user2)
482 conn = get(conn, "/api/v1/notifications?with_muted=true")
484 assert length(json_response_and_validate_schema(conn, 200)) == 1
487 @tag capture_log: true
488 test "see move notifications" do
489 old_user = insert(:user)
490 new_user = insert(:user, also_known_as: [old_user.ap_id])
491 %{user: follower, conn: conn} = oauth_access(["read:notifications"])
493 old_user_url = old_user.ap_id
496 File.read!("test/fixtures/users_mock/localhost.json")
497 |> String.replace("{{nickname}}", old_user.nickname)
501 %{method: :get, url: ^old_user_url} ->
502 %Tesla.Env{status: 200, body: body}
505 User.follow(follower, old_user)
506 Pleroma.Web.ActivityPub.ActivityPub.move(old_user, new_user)
507 Pleroma.Tests.ObanHelpers.perform_all()
509 conn = get(conn, "/api/v1/notifications")
511 assert length(json_response_and_validate_schema(conn, 200)) == 1
514 describe "link headers" do
515 test "preserves parameters in link headers" do
516 %{user: user, conn: conn} = oauth_access(["read:notifications"])
517 other_user = insert(:user)
520 CommonAPI.post(other_user, %{
521 "status" => "hi @#{user.nickname}",
522 "visibility" => "public"
526 CommonAPI.post(other_user, %{
527 "status" => "hi @#{user.nickname}",
528 "visibility" => "public"
531 notification1 = Repo.get_by(Notification, activity_id: activity1.id)
532 notification2 = Repo.get_by(Notification, activity_id: activity2.id)
536 |> assign(:user, user)
537 |> get("/api/v1/notifications?limit=5")
539 assert [link_header] = get_resp_header(conn, "link")
540 assert link_header =~ ~r/limit=5/
541 assert link_header =~ ~r/min_id=#{notification2.id}/
542 assert link_header =~ ~r/max_id=#{notification1.id}/
546 describe "from specified user" do
548 %{user: user, conn: conn} = oauth_access(["read:notifications"])
550 %{id: account_id} = other_user1 = insert(:user)
551 other_user2 = insert(:user)
553 {:ok, _activity} = CommonAPI.post(other_user1, %{"status" => "hi @#{user.nickname}"})
554 {:ok, _activity} = CommonAPI.post(other_user2, %{"status" => "bye @#{user.nickname}"})
556 assert [%{"account" => %{"id" => ^account_id}}] =
558 |> assign(:user, user)
559 |> get("/api/v1/notifications?account_id=#{account_id}")
560 |> json_response_and_validate_schema(200)
562 assert %{"error" => "Account is not found"} =
564 |> assign(:user, user)
565 |> get("/api/v1/notifications?account_id=cofe")
566 |> json_response_and_validate_schema(404)
570 defp get_notification_id_by_activity(%{id: id}) do
572 |> Repo.get_by(activity_id: id)
577 defp params_to_query(%{} = params) do
578 Enum.map_join(params, "&", fn
579 {k, v} when is_list(v) -> Enum.map_join(v, "&", &"#{k}[]=#{&1}")
580 {k, v} -> k <> "=" <> v