+
+ describe "set_read_up_to()" do
+ test "it sets all notifications as read up to a specified notification ID" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, _activity} =
+ CommonAPI.post(user, %{
+ "status" => "hey @#{other_user.nickname}!"
+ })
+
+ {:ok, _activity} =
+ CommonAPI.post(user, %{
+ "status" => "hey again @#{other_user.nickname}!"
+ })
+
+ [n2, n1] = notifs = Notification.for_user(other_user)
+ assert length(notifs) == 2
+
+ assert n2.id > n1.id
+
+ {:ok, _activity} =
+ CommonAPI.post(user, %{
+ "status" => "hey yet again @#{other_user.nickname}!"
+ })
+
+ Notification.set_read_up_to(other_user, n2.id)
+
+ [n3, n2, n1] = Notification.for_user(other_user)
+
+ assert n1.seen == true
+ assert n2.seen == true
+ assert n3.seen == false
+ end
+ end
+
+ describe "for_user_since/2" do
+ defp days_ago(days) do
+ NaiveDateTime.add(
+ NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second),
+ -days * 60 * 60 * 24,
+ :second
+ )
+ end
+
+ test "Returns recent notifications" do
+ user1 = insert(:user)
+ user2 = insert(:user)
+
+ Enum.each(0..10, fn i ->
+ {:ok, _activity} =
+ CommonAPI.post(user1, %{
+ "status" => "hey ##{i} @#{user2.nickname}!"
+ })
+ end)
+
+ {old, new} = Enum.split(Notification.for_user(user2), 5)
+
+ Enum.each(old, fn notification ->
+ notification
+ |> cast(%{updated_at: days_ago(10)}, [:updated_at])
+ |> Pleroma.Repo.update!()
+ end)
+
+ recent_notifications_ids =
+ user2
+ |> Notification.for_user_since(
+ NaiveDateTime.add(NaiveDateTime.utc_now(), -5 * 86_400, :second)
+ )
+ |> Enum.map(& &1.id)
+
+ Enum.each(old, fn %{id: id} ->
+ refute id in recent_notifications_ids
+ end)
+
+ Enum.each(new, fn %{id: id} ->
+ assert id in recent_notifications_ids
+ end)
+ end
+ end
+
+ describe "notification target determination" do
+ test "it sends notifications to addressed users in new messages" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{
+ "status" => "hey @#{other_user.nickname}!"
+ })
+
+ assert other_user in Notification.get_notified_from_activity(activity)
+ end
+
+ test "it sends notifications to mentioned users in new messages" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ create_activity = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "type" => "Create",
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "actor" => user.ap_id,
+ "object" => %{
+ "type" => "Note",
+ "content" => "message with a Mention tag, but no explicit tagging",
+ "tag" => [
+ %{
+ "type" => "Mention",
+ "href" => other_user.ap_id,
+ "name" => other_user.nickname
+ }
+ ],
+ "attributedTo" => user.ap_id
+ }
+ }
+
+ {:ok, activity} = Transmogrifier.handle_incoming(create_activity)
+
+ assert other_user in Notification.get_notified_from_activity(activity)
+ end
+
+ test "it does not send notifications to users who are only cc in new messages" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ create_activity = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "type" => "Create",
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "cc" => [other_user.ap_id],
+ "actor" => user.ap_id,
+ "object" => %{
+ "type" => "Note",
+ "content" => "hi everyone",
+ "attributedTo" => user.ap_id
+ }
+ }
+
+ {:ok, activity} = Transmogrifier.handle_incoming(create_activity)
+
+ assert other_user not in Notification.get_notified_from_activity(activity)
+ end
+
+ test "it does not send notification to mentioned users in likes" do
+ user = insert(:user)
+ other_user = insert(:user)
+ third_user = insert(:user)
+
+ {:ok, activity_one} =
+ CommonAPI.post(user, %{
+ "status" => "hey @#{other_user.nickname}!"
+ })
+
+ {:ok, activity_two, _} = CommonAPI.favorite(activity_one.id, third_user)
+
+ assert other_user not in Notification.get_notified_from_activity(activity_two)
+ end
+
+ test "it does not send notification to mentioned users in announces" do
+ user = insert(:user)
+ other_user = insert(:user)
+ third_user = insert(:user)
+
+ {:ok, activity_one} =
+ CommonAPI.post(user, %{
+ "status" => "hey @#{other_user.nickname}!"
+ })
+
+ {:ok, activity_two, _} = CommonAPI.repeat(activity_one.id, third_user)
+
+ assert other_user not in Notification.get_notified_from_activity(activity_two)
+ end
+ end
+
+ describe "notification lifecycle" do
+ test "liking an activity results in 1 notification, then 0 if the activity is deleted" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"})
+
+ assert Enum.empty?(Notification.for_user(user))
+
+ {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
+
+ assert length(Notification.for_user(user)) == 1
+
+ {:ok, _} = CommonAPI.delete(activity.id, user)
+
+ assert Enum.empty?(Notification.for_user(user))
+ end
+
+ test "liking an activity results in 1 notification, then 0 if the activity is unliked" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"})
+
+ assert Enum.empty?(Notification.for_user(user))
+
+ {:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
+
+ assert length(Notification.for_user(user)) == 1
+
+ {:ok, _, _, _} = CommonAPI.unfavorite(activity.id, other_user)
+
+ assert Enum.empty?(Notification.for_user(user))
+ end
+
+ test "repeating an activity results in 1 notification, then 0 if the activity is deleted" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"})
+
+ assert Enum.empty?(Notification.for_user(user))
+
+ {:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
+
+ assert length(Notification.for_user(user)) == 1
+
+ {:ok, _} = CommonAPI.delete(activity.id, user)
+
+ assert Enum.empty?(Notification.for_user(user))
+ end
+
+ test "repeating an activity results in 1 notification, then 0 if the activity is unrepeated" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"})
+
+ assert Enum.empty?(Notification.for_user(user))
+
+ {:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
+
+ assert length(Notification.for_user(user)) == 1
+
+ {:ok, _, _} = CommonAPI.unrepeat(activity.id, other_user)
+
+ assert Enum.empty?(Notification.for_user(user))
+ end
+
+ test "liking an activity which is already deleted does not generate a notification" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"})
+
+ assert Enum.empty?(Notification.for_user(user))
+
+ {:ok, _deletion_activity} = CommonAPI.delete(activity.id, user)
+
+ assert Enum.empty?(Notification.for_user(user))
+
+ {:error, _} = CommonAPI.favorite(activity.id, other_user)
+
+ assert Enum.empty?(Notification.for_user(user))
+ end
+
+ test "repeating an activity which is already deleted does not generate a notification" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"})
+
+ assert Enum.empty?(Notification.for_user(user))
+
+ {:ok, _deletion_activity} = CommonAPI.delete(activity.id, user)
+
+ assert Enum.empty?(Notification.for_user(user))
+
+ {:error, _} = CommonAPI.repeat(activity.id, other_user)
+
+ assert Enum.empty?(Notification.for_user(user))
+ end
+
+ test "replying to a deleted post without tagging does not generate a notification" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"})
+ {:ok, _deletion_activity} = CommonAPI.delete(activity.id, user)
+
+ {:ok, _reply_activity} =
+ CommonAPI.post(other_user, %{
+ "status" => "test reply",
+ "in_reply_to_status_id" => activity.id
+ })
+
+ assert Enum.empty?(Notification.for_user(user))
+ end
+
+ test "notifications are deleted if a local user is deleted" do
+ user = insert(:user)
+ other_user = insert(:user)
+
+ {:ok, _activity} =
+ CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}", "visibility" => "direct"})
+
+ refute Enum.empty?(Notification.for_user(other_user))
+
+ {:ok, job} = User.delete(user)
+ ObanHelpers.perform(job)
+
+ assert Enum.empty?(Notification.for_user(other_user))
+ end
+
+ test "notifications are deleted if a remote user is deleted" do
+ remote_user = insert(:user)
+ local_user = insert(:user)
+
+ dm_message = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "type" => "Create",
+ "actor" => remote_user.ap_id,
+ "id" => remote_user.ap_id <> "/activities/test",
+ "to" => [local_user.ap_id],
+ "cc" => [],
+ "object" => %{
+ "type" => "Note",
+ "content" => "Hello!",
+ "tag" => [
+ %{
+ "type" => "Mention",
+ "href" => local_user.ap_id,
+ "name" => "@#{local_user.nickname}"
+ }
+ ],
+ "to" => [local_user.ap_id],
+ "cc" => [],
+ "attributedTo" => remote_user.ap_id
+ }
+ }
+
+ {:ok, _dm_activity} = Transmogrifier.handle_incoming(dm_message)
+
+ refute Enum.empty?(Notification.for_user(local_user))
+
+ delete_user_message = %{
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "id" => remote_user.ap_id <> "/activities/delete",
+ "actor" => remote_user.ap_id,
+ "type" => "Delete",
+ "object" => remote_user.ap_id
+ }
+
+ {:ok, _delete_activity} = Transmogrifier.handle_incoming(delete_user_message)
+ ObanHelpers.perform_all()
+
+ assert Enum.empty?(Notification.for_user(local_user))
+ end
+ end
+
+ describe "for_user" do
+ test "it returns notifications for muted user without notifications" do
+ user = insert(:user)
+ muted = insert(:user)
+ {:ok, user} = User.mute(user, muted, false)
+
+ {:ok, _activity} = CommonAPI.post(muted, %{"status" => "hey @#{user.nickname}"})
+
+ assert length(Notification.for_user(user)) == 1
+ end
+
+ test "it doesn't return notifications for muted user with notifications" do
+ user = insert(:user)
+ muted = insert(:user)
+ {:ok, user} = User.mute(user, muted)
+
+ {:ok, _activity} = CommonAPI.post(muted, %{"status" => "hey @#{user.nickname}"})
+
+ assert Notification.for_user(user) == []
+ end
+
+ test "it doesn't return notifications for blocked user" do
+ user = insert(:user)
+ blocked = insert(:user)
+ {:ok, user} = User.block(user, blocked)
+
+ {:ok, _activity} = CommonAPI.post(blocked, %{"status" => "hey @#{user.nickname}"})
+
+ assert Notification.for_user(user) == []
+ end
+
+ test "it doesn't return notificatitons for blocked domain" do
+ user = insert(:user)
+ blocked = insert(:user, ap_id: "http://some-domain.com")
+ {:ok, user} = User.block_domain(user, "some-domain.com")
+
+ {:ok, _activity} = CommonAPI.post(blocked, %{"status" => "hey @#{user.nickname}"})
+
+ assert Notification.for_user(user) == []
+ end
+
+ test "it doesn't return notifications for muted thread" do
+ user = insert(:user)
+ another_user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(another_user, %{"status" => "hey @#{user.nickname}"})
+
+ {:ok, _} = Pleroma.ThreadMute.add_mute(user.id, activity.data["context"])
+ assert Notification.for_user(user) == []
+ end
+
+ test "it returns notifications for muted user with notifications and with_muted parameter" do
+ user = insert(:user)
+ muted = insert(:user)
+ {:ok, user} = User.mute(user, muted)
+
+ {:ok, _activity} = CommonAPI.post(muted, %{"status" => "hey @#{user.nickname}"})
+
+ assert length(Notification.for_user(user, %{with_muted: true})) == 1
+ end
+
+ test "it returns notifications for blocked user and with_muted parameter" do
+ user = insert(:user)
+ blocked = insert(:user)
+ {:ok, user} = User.block(user, blocked)
+
+ {:ok, _activity} = CommonAPI.post(blocked, %{"status" => "hey @#{user.nickname}"})
+
+ assert length(Notification.for_user(user, %{with_muted: true})) == 1
+ end
+
+ test "it returns notificatitons for blocked domain and with_muted parameter" do
+ user = insert(:user)
+ blocked = insert(:user, ap_id: "http://some-domain.com")
+ {:ok, user} = User.block_domain(user, "some-domain.com")
+
+ {:ok, _activity} = CommonAPI.post(blocked, %{"status" => "hey @#{user.nickname}"})
+
+ assert length(Notification.for_user(user, %{with_muted: true})) == 1
+ end
+
+ test "it returns notifications for muted thread with_muted parameter" do
+ user = insert(:user)
+ another_user = insert(:user)
+
+ {:ok, activity} = CommonAPI.post(another_user, %{"status" => "hey @#{user.nickname}"})
+
+ {:ok, _} = Pleroma.ThreadMute.add_mute(user.id, activity.data["context"])
+ assert length(Notification.for_user(user, %{with_muted: true})) == 1
+ end
+ end