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 "getting a single notification" do
58 %{user: user, conn: conn} = oauth_access(["read:notifications"])
59 other_user = insert(:user)
61 {:ok, activity} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
63 {:ok, [notification]} = Notification.create_notifications(activity)
65 conn = get(conn, "/api/v1/notifications/#{notification.id}")
68 "hi <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{user.id}\" href=\"#{
70 }\" rel=\"ugc\">@<span>#{user.nickname}</span></a></span>"
72 assert %{"status" => %{"content" => response}} = json_response_and_validate_schema(conn, 200)
73 assert response == expected_response
76 test "dismissing a single notification (deprecated endpoint)" do
77 %{user: user, conn: conn} = oauth_access(["write:notifications"])
78 other_user = insert(:user)
80 {:ok, activity} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
82 {:ok, [notification]} = Notification.create_notifications(activity)
86 |> assign(:user, user)
87 |> put_req_header("content-type", "application/json")
88 |> post("/api/v1/notifications/dismiss", %{"id" => to_string(notification.id)})
90 assert %{} = json_response_and_validate_schema(conn, 200)
93 test "dismissing a single notification" do
94 %{user: user, conn: conn} = oauth_access(["write:notifications"])
95 other_user = insert(:user)
97 {:ok, activity} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
99 {:ok, [notification]} = Notification.create_notifications(activity)
103 |> assign(:user, user)
104 |> post("/api/v1/notifications/#{notification.id}/dismiss")
106 assert %{} = json_response_and_validate_schema(conn, 200)
109 test "clearing all notifications" do
110 %{user: user, conn: conn} = oauth_access(["write:notifications", "read:notifications"])
111 other_user = insert(:user)
113 {:ok, activity} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
115 {:ok, [_notification]} = Notification.create_notifications(activity)
117 ret_conn = post(conn, "/api/v1/notifications/clear")
119 assert %{} = json_response_and_validate_schema(ret_conn, 200)
121 ret_conn = get(conn, "/api/v1/notifications")
123 assert all = json_response_and_validate_schema(ret_conn, 200)
127 test "paginates notifications using min_id, since_id, max_id, and limit" do
128 %{user: user, conn: conn} = oauth_access(["read:notifications"])
129 other_user = insert(:user)
131 {:ok, activity1} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
132 {:ok, activity2} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
133 {:ok, activity3} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
134 {:ok, activity4} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
136 notification1_id = get_notification_id_by_activity(activity1)
137 notification2_id = get_notification_id_by_activity(activity2)
138 notification3_id = get_notification_id_by_activity(activity3)
139 notification4_id = get_notification_id_by_activity(activity4)
141 conn = assign(conn, :user, user)
146 |> get("/api/v1/notifications?limit=2&min_id=#{notification1_id}")
147 |> json_response_and_validate_schema(:ok)
149 assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
154 |> get("/api/v1/notifications?limit=2&since_id=#{notification1_id}")
155 |> json_response_and_validate_schema(:ok)
157 assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
162 |> get("/api/v1/notifications?limit=2&max_id=#{notification4_id}")
163 |> json_response_and_validate_schema(:ok)
165 assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result
168 describe "exclude_visibilities" do
169 test "filters notifications for mentions" do
170 %{user: user, conn: conn} = oauth_access(["read:notifications"])
171 other_user = insert(:user)
173 {:ok, public_activity} =
174 CommonAPI.post(other_user, %{status: "@#{user.nickname}", visibility: "public"})
176 {:ok, direct_activity} =
177 CommonAPI.post(other_user, %{status: "@#{user.nickname}", visibility: "direct"})
179 {:ok, unlisted_activity} =
180 CommonAPI.post(other_user, %{status: "@#{user.nickname}", visibility: "unlisted"})
182 {:ok, private_activity} =
183 CommonAPI.post(other_user, %{status: "@#{user.nickname}", visibility: "private"})
185 query = params_to_query(%{exclude_visibilities: ["public", "unlisted", "private"]})
186 conn_res = get(conn, "/api/v1/notifications?" <> query)
188 assert [%{"status" => %{"id" => id}}] = json_response_and_validate_schema(conn_res, 200)
189 assert id == direct_activity.id
191 query = params_to_query(%{exclude_visibilities: ["public", "unlisted", "direct"]})
192 conn_res = get(conn, "/api/v1/notifications?" <> query)
194 assert [%{"status" => %{"id" => id}}] = json_response_and_validate_schema(conn_res, 200)
195 assert id == private_activity.id
197 query = params_to_query(%{exclude_visibilities: ["public", "private", "direct"]})
198 conn_res = get(conn, "/api/v1/notifications?" <> query)
200 assert [%{"status" => %{"id" => id}}] = json_response_and_validate_schema(conn_res, 200)
201 assert id == unlisted_activity.id
203 query = params_to_query(%{exclude_visibilities: ["unlisted", "private", "direct"]})
204 conn_res = get(conn, "/api/v1/notifications?" <> query)
206 assert [%{"status" => %{"id" => id}}] = json_response_and_validate_schema(conn_res, 200)
207 assert id == public_activity.id
210 test "filters notifications for Like activities" do
212 %{user: other_user, conn: conn} = oauth_access(["read:notifications"])
214 {:ok, public_activity} = CommonAPI.post(other_user, %{status: ".", visibility: "public"})
216 {:ok, direct_activity} =
217 CommonAPI.post(other_user, %{status: "@#{user.nickname}", visibility: "direct"})
219 {:ok, unlisted_activity} =
220 CommonAPI.post(other_user, %{status: ".", visibility: "unlisted"})
222 {:ok, private_activity} = CommonAPI.post(other_user, %{status: ".", visibility: "private"})
224 {:ok, _} = CommonAPI.favorite(user, public_activity.id)
225 {:ok, _} = CommonAPI.favorite(user, direct_activity.id)
226 {:ok, _} = CommonAPI.favorite(user, unlisted_activity.id)
227 {:ok, _} = CommonAPI.favorite(user, private_activity.id)
231 |> get("/api/v1/notifications?exclude_visibilities[]=direct")
232 |> json_response_and_validate_schema(200)
233 |> Enum.map(& &1["status"]["id"])
235 assert public_activity.id in activity_ids
236 assert unlisted_activity.id in activity_ids
237 assert private_activity.id in activity_ids
238 refute direct_activity.id in activity_ids
242 |> get("/api/v1/notifications?exclude_visibilities[]=unlisted")
243 |> json_response_and_validate_schema(200)
244 |> Enum.map(& &1["status"]["id"])
246 assert public_activity.id in activity_ids
247 refute unlisted_activity.id in activity_ids
248 assert private_activity.id in activity_ids
249 assert direct_activity.id in activity_ids
253 |> get("/api/v1/notifications?exclude_visibilities[]=private")
254 |> json_response_and_validate_schema(200)
255 |> Enum.map(& &1["status"]["id"])
257 assert public_activity.id in activity_ids
258 assert unlisted_activity.id in activity_ids
259 refute private_activity.id in activity_ids
260 assert direct_activity.id in activity_ids
264 |> get("/api/v1/notifications?exclude_visibilities[]=public")
265 |> json_response_and_validate_schema(200)
266 |> Enum.map(& &1["status"]["id"])
268 refute public_activity.id in activity_ids
269 assert unlisted_activity.id in activity_ids
270 assert private_activity.id in activity_ids
271 assert direct_activity.id in activity_ids
274 test "filters notifications for Announce activities" do
276 %{user: other_user, conn: conn} = oauth_access(["read:notifications"])
278 {:ok, public_activity} = CommonAPI.post(other_user, %{status: ".", visibility: "public"})
280 {:ok, unlisted_activity} =
281 CommonAPI.post(other_user, %{status: ".", visibility: "unlisted"})
283 {:ok, _, _} = CommonAPI.repeat(public_activity.id, user)
284 {:ok, _, _} = CommonAPI.repeat(unlisted_activity.id, user)
288 |> get("/api/v1/notifications?exclude_visibilities[]=unlisted")
289 |> json_response_and_validate_schema(200)
290 |> Enum.map(& &1["status"]["id"])
292 assert public_activity.id in activity_ids
293 refute unlisted_activity.id in activity_ids
297 test "filters notifications using exclude_types" do
298 %{user: user, conn: conn} = oauth_access(["read:notifications"])
299 other_user = insert(:user)
301 {:ok, mention_activity} = CommonAPI.post(other_user, %{status: "hey @#{user.nickname}"})
302 {:ok, create_activity} = CommonAPI.post(user, %{status: "hey"})
303 {:ok, favorite_activity} = CommonAPI.favorite(other_user, create_activity.id)
304 {:ok, reblog_activity, _} = CommonAPI.repeat(create_activity.id, other_user)
305 {:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user)
307 mention_notification_id = get_notification_id_by_activity(mention_activity)
308 favorite_notification_id = get_notification_id_by_activity(favorite_activity)
309 reblog_notification_id = get_notification_id_by_activity(reblog_activity)
310 follow_notification_id = get_notification_id_by_activity(follow_activity)
312 query = params_to_query(%{exclude_types: ["mention", "favourite", "reblog"]})
313 conn_res = get(conn, "/api/v1/notifications?" <> query)
315 assert [%{"id" => ^follow_notification_id}] = json_response_and_validate_schema(conn_res, 200)
317 query = params_to_query(%{exclude_types: ["favourite", "reblog", "follow"]})
318 conn_res = get(conn, "/api/v1/notifications?" <> query)
320 assert [%{"id" => ^mention_notification_id}] =
321 json_response_and_validate_schema(conn_res, 200)
323 query = params_to_query(%{exclude_types: ["reblog", "follow", "mention"]})
324 conn_res = get(conn, "/api/v1/notifications?" <> query)
326 assert [%{"id" => ^favorite_notification_id}] =
327 json_response_and_validate_schema(conn_res, 200)
329 query = params_to_query(%{exclude_types: ["follow", "mention", "favourite"]})
330 conn_res = get(conn, "/api/v1/notifications?" <> query)
332 assert [%{"id" => ^reblog_notification_id}] = json_response_and_validate_schema(conn_res, 200)
335 test "filters notifications using include_types" do
336 %{user: user, conn: conn} = oauth_access(["read:notifications"])
337 other_user = insert(:user)
339 {:ok, mention_activity} = CommonAPI.post(other_user, %{status: "hey @#{user.nickname}"})
340 {:ok, create_activity} = CommonAPI.post(user, %{status: "hey"})
341 {:ok, favorite_activity} = CommonAPI.favorite(other_user, create_activity.id)
342 {:ok, reblog_activity, _} = CommonAPI.repeat(create_activity.id, other_user)
343 {:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user)
345 mention_notification_id = get_notification_id_by_activity(mention_activity)
346 favorite_notification_id = get_notification_id_by_activity(favorite_activity)
347 reblog_notification_id = get_notification_id_by_activity(reblog_activity)
348 follow_notification_id = get_notification_id_by_activity(follow_activity)
350 conn_res = get(conn, "/api/v1/notifications?include_types[]=follow")
352 assert [%{"id" => ^follow_notification_id}] = json_response_and_validate_schema(conn_res, 200)
354 conn_res = get(conn, "/api/v1/notifications?include_types[]=mention")
356 assert [%{"id" => ^mention_notification_id}] =
357 json_response_and_validate_schema(conn_res, 200)
359 conn_res = get(conn, "/api/v1/notifications?include_types[]=favourite")
361 assert [%{"id" => ^favorite_notification_id}] =
362 json_response_and_validate_schema(conn_res, 200)
364 conn_res = get(conn, "/api/v1/notifications?include_types[]=reblog")
366 assert [%{"id" => ^reblog_notification_id}] = json_response_and_validate_schema(conn_res, 200)
368 result = conn |> get("/api/v1/notifications") |> json_response_and_validate_schema(200)
370 assert length(result) == 4
372 query = params_to_query(%{include_types: ["follow", "mention", "favourite", "reblog"]})
376 |> get("/api/v1/notifications?" <> query)
377 |> json_response_and_validate_schema(200)
379 assert length(result) == 4
382 test "destroy multiple" do
383 %{user: user, conn: conn} = oauth_access(["read:notifications", "write:notifications"])
384 other_user = insert(:user)
386 {:ok, activity1} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
387 {:ok, activity2} = CommonAPI.post(other_user, %{status: "hi @#{user.nickname}"})
388 {:ok, activity3} = CommonAPI.post(user, %{status: "hi @#{other_user.nickname}"})
389 {:ok, activity4} = CommonAPI.post(user, %{status: "hi @#{other_user.nickname}"})
391 notification1_id = get_notification_id_by_activity(activity1)
392 notification2_id = get_notification_id_by_activity(activity2)
393 notification3_id = get_notification_id_by_activity(activity3)
394 notification4_id = get_notification_id_by_activity(activity4)
398 |> get("/api/v1/notifications")
399 |> json_response_and_validate_schema(:ok)
401 assert [%{"id" => ^notification2_id}, %{"id" => ^notification1_id}] = result
405 |> assign(:user, other_user)
406 |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:notifications"]))
410 |> get("/api/v1/notifications")
411 |> json_response_and_validate_schema(:ok)
413 assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
415 query = params_to_query(%{ids: [notification1_id, notification2_id]})
416 conn_destroy = delete(conn, "/api/v1/notifications/destroy_multiple?" <> query)
418 assert json_response_and_validate_schema(conn_destroy, 200) == %{}
422 |> get("/api/v1/notifications")
423 |> json_response_and_validate_schema(:ok)
425 assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
428 test "doesn't see notifications after muting user with notifications" do
429 %{user: user, conn: conn} = oauth_access(["read:notifications"])
430 user2 = insert(:user)
432 {:ok, _, _, _} = CommonAPI.follow(user, user2)
433 {:ok, _} = CommonAPI.post(user2, %{status: "hey @#{user.nickname}"})
435 ret_conn = get(conn, "/api/v1/notifications")
437 assert length(json_response_and_validate_schema(ret_conn, 200)) == 1
439 {:ok, _user_relationships} = User.mute(user, user2)
441 conn = get(conn, "/api/v1/notifications")
443 assert json_response_and_validate_schema(conn, 200) == []
446 test "see notifications after muting user without notifications" do
447 %{user: user, conn: conn} = oauth_access(["read:notifications"])
448 user2 = insert(:user)
450 {:ok, _, _, _} = CommonAPI.follow(user, user2)
451 {:ok, _} = CommonAPI.post(user2, %{status: "hey @#{user.nickname}"})
453 ret_conn = get(conn, "/api/v1/notifications")
455 assert length(json_response_and_validate_schema(ret_conn, 200)) == 1
457 {:ok, _user_relationships} = User.mute(user, user2, false)
459 conn = get(conn, "/api/v1/notifications")
461 assert length(json_response_and_validate_schema(conn, 200)) == 1
464 test "see notifications after muting user with notifications and with_muted parameter" do
465 %{user: user, conn: conn} = oauth_access(["read:notifications"])
466 user2 = insert(:user)
468 {:ok, _, _, _} = CommonAPI.follow(user, user2)
469 {:ok, _} = CommonAPI.post(user2, %{status: "hey @#{user.nickname}"})
471 ret_conn = get(conn, "/api/v1/notifications")
473 assert length(json_response_and_validate_schema(ret_conn, 200)) == 1
475 {:ok, _user_relationships} = User.mute(user, user2)
477 conn = get(conn, "/api/v1/notifications?with_muted=true")
479 assert length(json_response_and_validate_schema(conn, 200)) == 1
482 @tag capture_log: true
483 test "see move notifications" do
484 old_user = insert(:user)
485 new_user = insert(:user, also_known_as: [old_user.ap_id])
486 %{user: follower, conn: conn} = oauth_access(["read:notifications"])
488 old_user_url = old_user.ap_id
491 File.read!("test/fixtures/users_mock/localhost.json")
492 |> String.replace("{{nickname}}", old_user.nickname)
496 %{method: :get, url: ^old_user_url} ->
497 %Tesla.Env{status: 200, body: body}
500 User.follow(follower, old_user)
501 Pleroma.Web.ActivityPub.ActivityPub.move(old_user, new_user)
502 Pleroma.Tests.ObanHelpers.perform_all()
504 conn = get(conn, "/api/v1/notifications")
506 assert length(json_response_and_validate_schema(conn, 200)) == 1
509 describe "link headers" do
510 test "preserves parameters in link headers" do
511 %{user: user, conn: conn} = oauth_access(["read:notifications"])
512 other_user = insert(:user)
515 CommonAPI.post(other_user, %{
516 status: "hi @#{user.nickname}",
521 CommonAPI.post(other_user, %{
522 status: "hi @#{user.nickname}",
526 notification1 = Repo.get_by(Notification, activity_id: activity1.id)
527 notification2 = Repo.get_by(Notification, activity_id: activity2.id)
531 |> assign(:user, user)
532 |> get("/api/v1/notifications?limit=5")
534 assert [link_header] = get_resp_header(conn, "link")
535 assert link_header =~ ~r/limit=5/
536 assert link_header =~ ~r/min_id=#{notification2.id}/
537 assert link_header =~ ~r/max_id=#{notification1.id}/
541 describe "from specified user" do
543 %{user: user, conn: conn} = oauth_access(["read:notifications"])
545 %{id: account_id} = other_user1 = insert(:user)
546 other_user2 = insert(:user)
548 {:ok, _activity} = CommonAPI.post(other_user1, %{status: "hi @#{user.nickname}"})
549 {:ok, _activity} = CommonAPI.post(other_user2, %{status: "bye @#{user.nickname}"})
551 assert [%{"account" => %{"id" => ^account_id}}] =
553 |> assign(:user, user)
554 |> get("/api/v1/notifications?account_id=#{account_id}")
555 |> json_response_and_validate_schema(200)
557 assert %{"error" => "Account is not found"} =
559 |> assign(:user, user)
560 |> get("/api/v1/notifications?account_id=cofe")
561 |> json_response_and_validate_schema(404)
565 defp get_notification_id_by_activity(%{id: id}) do
567 |> Repo.get_by(activity_id: id)
572 defp params_to_query(%{} = params) do
573 Enum.map_join(params, "&", fn
574 {k, v} when is_list(v) -> Enum.map_join(v, "&", &"#{k}[]=#{&1}")
575 {k, v} -> k <> "=" <> v