Feature/1072 muting notifications
[akkoma] / test / web / mastodon_api / mastodon_api_controller_test.exs
index 17e723528ba6f2724f9aa8647a88645090d74828..6ffa64dc8bdf192c0d7956d545be79cb8d1f9a4d 100644 (file)
@@ -24,6 +24,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
   import ExUnit.CaptureLog
   import Tesla.Mock
 
+  @image "data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7"
+
   setup do
     mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
     :ok
@@ -94,56 +96,186 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
            |> json_response(403) == %{"error" => "This resource requires authentication."}
   end
 
-  test "posting a status", %{conn: conn} do
-    user = insert(:user)
+  describe "posting statuses" do
+    setup do
+      user = insert(:user)
 
-    idempotency_key = "Pikachu rocks!"
+      conn =
+        build_conn()
+        |> assign(:user, user)
 
-    conn_one =
-      conn
-      |> assign(:user, user)
-      |> put_req_header("idempotency-key", idempotency_key)
-      |> post("/api/v1/statuses", %{
-        "status" => "cofe",
-        "spoiler_text" => "2hu",
-        "sensitive" => "false"
-      })
+      [conn: conn]
+    end
 
-    {:ok, ttl} = Cachex.ttl(:idempotency_cache, idempotency_key)
-    # Six hours
-    assert ttl > :timer.seconds(6 * 60 * 60 - 1)
+    test "posting a status", %{conn: conn} do
+      idempotency_key = "Pikachu rocks!"
 
-    assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} =
-             json_response(conn_one, 200)
+      conn_one =
+        conn
+        |> put_req_header("idempotency-key", idempotency_key)
+        |> post("/api/v1/statuses", %{
+          "status" => "cofe",
+          "spoiler_text" => "2hu",
+          "sensitive" => "false"
+        })
 
-    assert Activity.get_by_id(id)
+      {:ok, ttl} = Cachex.ttl(:idempotency_cache, idempotency_key)
+      # Six hours
+      assert ttl > :timer.seconds(6 * 60 * 60 - 1)
 
-    conn_two =
-      conn
-      |> assign(:user, user)
-      |> put_req_header("idempotency-key", idempotency_key)
-      |> post("/api/v1/statuses", %{
-        "status" => "cofe",
-        "spoiler_text" => "2hu",
-        "sensitive" => "false"
-      })
+      assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} =
+               json_response(conn_one, 200)
 
-    assert %{"id" => second_id} = json_response(conn_two, 200)
+      assert Activity.get_by_id(id)
 
-    assert id == second_id
+      conn_two =
+        conn
+        |> put_req_header("idempotency-key", idempotency_key)
+        |> post("/api/v1/statuses", %{
+          "status" => "cofe",
+          "spoiler_text" => "2hu",
+          "sensitive" => "false"
+        })
 
-    conn_three =
-      conn
-      |> assign(:user, user)
-      |> post("/api/v1/statuses", %{
-        "status" => "cofe",
-        "spoiler_text" => "2hu",
-        "sensitive" => "false"
-      })
+      assert %{"id" => second_id} = json_response(conn_two, 200)
+      assert id == second_id
+
+      conn_three =
+        conn
+        |> post("/api/v1/statuses", %{
+          "status" => "cofe",
+          "spoiler_text" => "2hu",
+          "sensitive" => "false"
+        })
+
+      assert %{"id" => third_id} = json_response(conn_three, 200)
+      refute id == third_id
+    end
+
+    test "replying to a status", %{conn: conn} do
+      user = insert(:user)
+      {:ok, replied_to} = CommonAPI.post(user, %{"status" => "cofe"})
+
+      conn =
+        conn
+        |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
+
+      assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
+
+      activity = Activity.get_by_id(id)
+
+      assert activity.data["context"] == replied_to.data["context"]
+      assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
+    end
+
+    test "replying to a direct message with visibility other than direct", %{conn: conn} do
+      user = insert(:user)
+      {:ok, replied_to} = CommonAPI.post(user, %{"status" => "suya..", "visibility" => "direct"})
+
+      Enum.each(["public", "private", "unlisted"], fn visibility ->
+        conn =
+          conn
+          |> post("/api/v1/statuses", %{
+            "status" => "@#{user.nickname} hey",
+            "in_reply_to_id" => replied_to.id,
+            "visibility" => visibility
+          })
+
+        assert json_response(conn, 422) == %{"error" => "The message visibility must be direct"}
+      end)
+    end
+
+    test "posting a status with an invalid in_reply_to_id", %{conn: conn} do
+      conn =
+        conn
+        |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => ""})
+
+      assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
+      assert Activity.get_by_id(id)
+    end
+
+    test "posting a sensitive status", %{conn: conn} do
+      conn =
+        conn
+        |> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true})
+
+      assert %{"content" => "cofe", "id" => id, "sensitive" => true} = json_response(conn, 200)
+      assert Activity.get_by_id(id)
+    end
+
+    test "posting a fake status", %{conn: conn} do
+      real_conn =
+        conn
+        |> post("/api/v1/statuses", %{
+          "status" =>
+            "\"Tenshi Eating a Corndog\" is a much discussed concept on /jp/. The significance of it is disputed, so I will focus on one core concept: the symbolism behind it"
+        })
+
+      real_status = json_response(real_conn, 200)
+
+      assert real_status
+      assert Object.get_by_ap_id(real_status["uri"])
+
+      real_status =
+        real_status
+        |> Map.put("id", nil)
+        |> Map.put("url", nil)
+        |> Map.put("uri", nil)
+        |> Map.put("created_at", nil)
+        |> Kernel.put_in(["pleroma", "conversation_id"], nil)
+
+      fake_conn =
+        conn
+        |> post("/api/v1/statuses", %{
+          "status" =>
+            "\"Tenshi Eating a Corndog\" is a much discussed concept on /jp/. The significance of it is disputed, so I will focus on one core concept: the symbolism behind it",
+          "preview" => true
+        })
+
+      fake_status = json_response(fake_conn, 200)
+
+      assert fake_status
+      refute Object.get_by_ap_id(fake_status["uri"])
+
+      fake_status =
+        fake_status
+        |> Map.put("id", nil)
+        |> Map.put("url", nil)
+        |> Map.put("uri", nil)
+        |> Map.put("created_at", nil)
+        |> Kernel.put_in(["pleroma", "conversation_id"], nil)
+
+      assert real_status == fake_status
+    end
+
+    test "posting a status with OGP link preview", %{conn: conn} do
+      Pleroma.Config.put([:rich_media, :enabled], true)
+
+      conn =
+        conn
+        |> post("/api/v1/statuses", %{
+          "status" => "https://example.com/ogp"
+        })
 
-    assert %{"id" => third_id} = json_response(conn_three, 200)
+      assert %{"id" => id, "card" => %{"title" => "The Rock"}} = json_response(conn, 200)
+      assert Activity.get_by_id(id)
+      Pleroma.Config.put([:rich_media, :enabled], false)
+    end
 
-    refute id == third_id
+    test "posting a direct status", %{conn: conn} do
+      user2 = insert(:user)
+      content = "direct cofe @#{user2.nickname}"
+
+      conn =
+        conn
+        |> post("api/v1/statuses", %{"status" => content, "visibility" => "direct"})
+
+      assert %{"id" => id, "visibility" => "direct"} = json_response(conn, 200)
+      assert activity = Activity.get_by_id(id)
+      assert activity.recipients == [user2.ap_id, conn.assigns[:user].ap_id]
+      assert activity.data["to"] == [user2.ap_id]
+      assert activity.data["cc"] == []
+    end
   end
 
   describe "posting polls" do
@@ -243,100 +375,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
     end
   end
 
-  test "posting a sensitive status", %{conn: conn} do
-    user = insert(:user)
-
-    conn =
-      conn
-      |> assign(:user, user)
-      |> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true})
-
-    assert %{"content" => "cofe", "id" => id, "sensitive" => true} = json_response(conn, 200)
-    assert Activity.get_by_id(id)
-  end
-
-  test "posting a fake status", %{conn: conn} do
-    user = insert(:user)
-
-    real_conn =
-      conn
-      |> assign(:user, user)
-      |> post("/api/v1/statuses", %{
-        "status" =>
-          "\"Tenshi Eating a Corndog\" is a much discussed concept on /jp/. The significance of it is disputed, so I will focus on one core concept: the symbolism behind it"
-      })
-
-    real_status = json_response(real_conn, 200)
-
-    assert real_status
-    assert Object.get_by_ap_id(real_status["uri"])
-
-    real_status =
-      real_status
-      |> Map.put("id", nil)
-      |> Map.put("url", nil)
-      |> Map.put("uri", nil)
-      |> Map.put("created_at", nil)
-      |> Kernel.put_in(["pleroma", "conversation_id"], nil)
-
-    fake_conn =
-      conn
-      |> assign(:user, user)
-      |> post("/api/v1/statuses", %{
-        "status" =>
-          "\"Tenshi Eating a Corndog\" is a much discussed concept on /jp/. The significance of it is disputed, so I will focus on one core concept: the symbolism behind it",
-        "preview" => true
-      })
-
-    fake_status = json_response(fake_conn, 200)
-
-    assert fake_status
-    refute Object.get_by_ap_id(fake_status["uri"])
-
-    fake_status =
-      fake_status
-      |> Map.put("id", nil)
-      |> Map.put("url", nil)
-      |> Map.put("uri", nil)
-      |> Map.put("created_at", nil)
-      |> Kernel.put_in(["pleroma", "conversation_id"], nil)
-
-    assert real_status == fake_status
-  end
-
-  test "posting a status with OGP link preview", %{conn: conn} do
-    Pleroma.Config.put([:rich_media, :enabled], true)
-    user = insert(:user)
-
-    conn =
-      conn
-      |> assign(:user, user)
-      |> post("/api/v1/statuses", %{
-        "status" => "https://example.com/ogp"
-      })
-
-    assert %{"id" => id, "card" => %{"title" => "The Rock"}} = json_response(conn, 200)
-    assert Activity.get_by_id(id)
-    Pleroma.Config.put([:rich_media, :enabled], false)
-  end
-
-  test "posting a direct status", %{conn: conn} do
-    user1 = insert(:user)
-    user2 = insert(:user)
-    content = "direct cofe @#{user2.nickname}"
-
-    conn =
-      conn
-      |> assign(:user, user1)
-      |> post("api/v1/statuses", %{"status" => content, "visibility" => "direct"})
-
-    assert %{"id" => id, "visibility" => "direct"} = json_response(conn, 200)
-    assert activity = Activity.get_by_id(id)
-    assert activity.recipients == [user2.ap_id, user1.ap_id]
-    assert activity.data["to"] == [user2.ap_id]
-    assert activity.data["cc"] == []
-  end
-
   test "direct timeline", %{conn: conn} do
     user_one = insert(:user)
     user_two = insert(:user)
@@ -501,39 +539,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
     assert status["id"] == direct.id
   end
 
-  test "replying to a status", %{conn: conn} do
-    user = insert(:user)
-
-    {:ok, replied_to} = TwitterAPI.create_status(user, %{"status" => "cofe"})
-
-    conn =
-      conn
-      |> assign(:user, user)
-      |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id})
-
-    assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
-
-    activity = Activity.get_by_id(id)
-
-    assert activity.data["context"] == replied_to.data["context"]
-    assert Activity.get_in_reply_to_activity(activity).id == replied_to.id
-  end
-
-  test "posting a status with an invalid in_reply_to_id", %{conn: conn} do
-    user = insert(:user)
-
-    conn =
-      conn
-      |> assign(:user, user)
-      |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => ""})
-
-    assert %{"content" => "xD", "id" => id} = json_response(conn, 200)
-
-    activity = Activity.get_by_id(id)
-
-    assert activity
-  end
-
   test "verify_credentials", %{conn: conn} do
     user = insert(:user)
 
@@ -581,6 +586,101 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
     assert expected == json_response(conn, 200)
   end
 
+  test "user avatar can be set", %{conn: conn} do
+    user = insert(:user)
+    avatar_image = File.read!("test/fixtures/avatar_data_uri")
+
+    conn =
+      conn
+      |> assign(:user, user)
+      |> patch("/api/v1/pleroma/accounts/update_avatar", %{img: avatar_image})
+
+    user = refresh_record(user)
+
+    assert %{
+             "name" => _,
+             "type" => _,
+             "url" => [
+               %{
+                 "href" => _,
+                 "mediaType" => _,
+                 "type" => _
+               }
+             ]
+           } = user.avatar
+
+    assert %{"url" => _} = json_response(conn, 200)
+  end
+
+  test "user avatar can be reset", %{conn: conn} do
+    user = insert(:user)
+
+    conn =
+      conn
+      |> assign(:user, user)
+      |> patch("/api/v1/pleroma/accounts/update_avatar", %{img: ""})
+
+    user = User.get_cached_by_id(user.id)
+
+    assert user.avatar == nil
+
+    assert %{"url" => nil} = json_response(conn, 200)
+  end
+
+  test "can set profile banner", %{conn: conn} do
+    user = insert(:user)
+
+    conn =
+      conn
+      |> assign(:user, user)
+      |> patch("/api/v1/pleroma/accounts/update_banner", %{"banner" => @image})
+
+    user = refresh_record(user)
+    assert user.info.banner["type"] == "Image"
+
+    assert %{"url" => _} = json_response(conn, 200)
+  end
+
+  test "can reset profile banner", %{conn: conn} do
+    user = insert(:user)
+
+    conn =
+      conn
+      |> assign(:user, user)
+      |> patch("/api/v1/pleroma/accounts/update_banner", %{"banner" => ""})
+
+    user = refresh_record(user)
+    assert user.info.banner == %{}
+
+    assert %{"url" => nil} = json_response(conn, 200)
+  end
+
+  test "background image can be set", %{conn: conn} do
+    user = insert(:user)
+
+    conn =
+      conn
+      |> assign(:user, user)
+      |> patch("/api/v1/pleroma/accounts/update_background", %{"img" => @image})
+
+    user = refresh_record(user)
+    assert user.info.background["type"] == "Image"
+    assert %{"url" => _} = json_response(conn, 200)
+  end
+
+  test "background image can be reset", %{conn: conn} do
+    user = insert(:user)
+
+    conn =
+      conn
+      |> assign(:user, user)
+      |> patch("/api/v1/pleroma/accounts/update_background", %{"img" => ""})
+
+    user = refresh_record(user)
+    assert user.info.background == %{}
+    assert %{"url" => nil} = json_response(conn, 200)
+  end
+
   test "creates an oauth app", %{conn: conn} do
     user = insert(:user)
     app_attrs = build(:oauth_app)
@@ -1174,6 +1274,71 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
       result = json_response(conn_res, 200)
       assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result
     end
+
+    test "doesn't see notifications after muting user with notifications", %{conn: conn} do
+      user = insert(:user)
+      user2 = insert(:user)
+
+      {:ok, _, _, _} = CommonAPI.follow(user, user2)
+      {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"})
+
+      conn = assign(conn, :user, user)
+
+      conn = get(conn, "/api/v1/notifications")
+
+      assert length(json_response(conn, 200)) == 1
+
+      {:ok, user} = User.mute(user, user2)
+
+      conn = assign(build_conn(), :user, user)
+      conn = get(conn, "/api/v1/notifications")
+
+      assert json_response(conn, 200) == []
+    end
+
+    test "see notifications after muting user without notifications", %{conn: conn} do
+      user = insert(:user)
+      user2 = insert(:user)
+
+      {:ok, _, _, _} = CommonAPI.follow(user, user2)
+      {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"})
+
+      conn = assign(conn, :user, user)
+
+      conn = get(conn, "/api/v1/notifications")
+
+      assert length(json_response(conn, 200)) == 1
+
+      {:ok, user} = User.mute(user, user2, false)
+
+      conn = assign(build_conn(), :user, user)
+      conn = get(conn, "/api/v1/notifications")
+
+      assert length(json_response(conn, 200)) == 1
+    end
+
+    test "see notifications after muting user with notifications and with_muted parameter", %{
+      conn: conn
+    } do
+      user = insert(:user)
+      user2 = insert(:user)
+
+      {:ok, _, _, _} = CommonAPI.follow(user, user2)
+      {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"})
+
+      conn = assign(conn, :user, user)
+
+      conn = get(conn, "/api/v1/notifications")
+
+      assert length(json_response(conn, 200)) == 1
+
+      {:ok, user} = User.mute(user, user2)
+
+      conn = assign(build_conn(), :user, user)
+      conn = get(conn, "/api/v1/notifications", %{"with_muted" => "true"})
+
+      assert length(json_response(conn, 200)) == 1
+    end
   end
 
   describe "reblogging" do
@@ -1405,6 +1570,19 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
       assert [%{"id" => id}] = json_response(conn, 200)
       assert id == to_string(post.id)
     end
+
+    test "filters user's statuses by a hashtag", %{conn: conn} do
+      user = insert(:user)
+      {:ok, post} = CommonAPI.post(user, %{"status" => "#hashtag"})
+      {:ok, _post} = CommonAPI.post(user, %{"status" => "hashtag"})
+
+      conn =
+        conn
+        |> get("/api/v1/accounts/#{user.id}/statuses", %{"tagged" => "hashtag"})
+
+      assert [%{"id" => id}] = json_response(conn, 200)
+      assert id == to_string(post.id)
+    end
   end
 
   describe "user relationships" do
@@ -1992,25 +2170,52 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
     assert %{"error" => "Record not found"} = json_response(conn_res, 404)
   end
 
-  test "muting / unmuting a user", %{conn: conn} do
-    user = insert(:user)
-    other_user = insert(:user)
+  describe "mute/unmute" do
+    test "with notifications", %{conn: conn} do
+      user = insert(:user)
+      other_user = insert(:user)
 
-    conn =
-      conn
-      |> assign(:user, user)
-      |> post("/api/v1/accounts/#{other_user.id}/mute")
+      conn =
+        conn
+        |> assign(:user, user)
+        |> post("/api/v1/accounts/#{other_user.id}/mute")
 
-    assert %{"id" => _id, "muting" => true} = json_response(conn, 200)
+      response = json_response(conn, 200)
 
-    user = User.get_cached_by_id(user.id)
+      assert %{"id" => _id, "muting" => true, "muting_notifications" => true} = response
+      user = User.get_cached_by_id(user.id)
 
-    conn =
-      build_conn()
-      |> assign(:user, user)
-      |> post("/api/v1/accounts/#{other_user.id}/unmute")
+      conn =
+        build_conn()
+        |> assign(:user, user)
+        |> post("/api/v1/accounts/#{other_user.id}/unmute")
+
+      response = json_response(conn, 200)
+      assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response
+    end
+
+    test "without notifications", %{conn: conn} do
+      user = insert(:user)
+      other_user = insert(:user)
+
+      conn =
+        conn
+        |> assign(:user, user)
+        |> post("/api/v1/accounts/#{other_user.id}/mute", %{"notifications" => "false"})
+
+      response = json_response(conn, 200)
+
+      assert %{"id" => _id, "muting" => true, "muting_notifications" => false} = response
+      user = User.get_cached_by_id(user.id)
 
-    assert %{"id" => _id, "muting" => false} = json_response(conn, 200)
+      conn =
+        build_conn()
+        |> assign(:user, user)
+        |> post("/api/v1/accounts/#{other_user.id}/unmute")
+
+      response = json_response(conn, 200)
+      assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response
+    end
   end
 
   test "subscribing / unsubscribing to a user", %{conn: conn} do
@@ -2845,6 +3050,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
       assert Map.has_key?(emoji, "static_url")
       assert Map.has_key?(emoji, "tags")
       assert is_list(emoji["tags"])
+      assert Map.has_key?(emoji, "category")
       assert Map.has_key?(emoji, "url")
       assert Map.has_key?(emoji, "visible_in_picker")
     end