Merge branch 'develop' into 'remove-avatar-header'
[akkoma] / test / web / mastodon_api / mastodon_api_controller_test.exs
index 90ca26441235e675a9c4b65b0d87f43c288f30f2..de157d5297878ef53b6e9404e3e43e88a59b9d29 100644 (file)
@@ -148,6 +148,103 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
     refute id == third_id
   end
 
+  describe "posting polls" do
+    test "posting a poll", %{conn: conn} do
+      user = insert(:user)
+      time = NaiveDateTime.utc_now()
+
+      conn =
+        conn
+        |> assign(:user, user)
+        |> post("/api/v1/statuses", %{
+          "status" => "Who is the #bestgrill?",
+          "poll" => %{"options" => ["Rei", "Asuka", "Misato"], "expires_in" => 420}
+        })
+
+      response = json_response(conn, 200)
+
+      assert Enum.all?(response["poll"]["options"], fn %{"title" => title} ->
+               title in ["Rei", "Asuka", "Misato"]
+             end)
+
+      assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430
+      refute response["poll"]["expred"]
+    end
+
+    test "option limit is enforced", %{conn: conn} do
+      user = insert(:user)
+      limit = Pleroma.Config.get([:instance, :poll_limits, :max_options])
+
+      conn =
+        conn
+        |> assign(:user, user)
+        |> post("/api/v1/statuses", %{
+          "status" => "desu~",
+          "poll" => %{"options" => Enum.map(0..limit, fn _ -> "desu" end), "expires_in" => 1}
+        })
+
+      %{"error" => error} = json_response(conn, 422)
+      assert error == "Poll can't contain more than #{limit} options"
+    end
+
+    test "option character limit is enforced", %{conn: conn} do
+      user = insert(:user)
+      limit = Pleroma.Config.get([:instance, :poll_limits, :max_option_chars])
+
+      conn =
+        conn
+        |> assign(:user, user)
+        |> post("/api/v1/statuses", %{
+          "status" => "...",
+          "poll" => %{
+            "options" => [Enum.reduce(0..limit, "", fn _, acc -> acc <> "." end)],
+            "expires_in" => 1
+          }
+        })
+
+      %{"error" => error} = json_response(conn, 422)
+      assert error == "Poll options cannot be longer than #{limit} characters each"
+    end
+
+    test "minimal date limit is enforced", %{conn: conn} do
+      user = insert(:user)
+      limit = Pleroma.Config.get([:instance, :poll_limits, :min_expiration])
+
+      conn =
+        conn
+        |> assign(:user, user)
+        |> post("/api/v1/statuses", %{
+          "status" => "imagine arbitrary limits",
+          "poll" => %{
+            "options" => ["this post was made by pleroma gang"],
+            "expires_in" => limit - 1
+          }
+        })
+
+      %{"error" => error} = json_response(conn, 422)
+      assert error == "Expiration date is too soon"
+    end
+
+    test "maximum date limit is enforced", %{conn: conn} do
+      user = insert(:user)
+      limit = Pleroma.Config.get([:instance, :poll_limits, :max_expiration])
+
+      conn =
+        conn
+        |> assign(:user, user)
+        |> post("/api/v1/statuses", %{
+          "status" => "imagine arbitrary limits",
+          "poll" => %{
+            "options" => ["this post was made by pleroma gang"],
+            "expires_in" => limit + 1
+          }
+        })
+
+      %{"error" => error} = json_response(conn, 422)
+      assert error == "Expiration date is too far in the future"
+    end
+  end
+
   test "posting a sensitive status", %{conn: conn} do
     user = insert(:user)
 
@@ -319,12 +416,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
   test "Conversations", %{conn: conn} do
     user_one = insert(:user)
     user_two = insert(:user)
+    user_three = insert(:user)
 
     {:ok, user_two} = User.follow(user_two, user_one)
 
     {:ok, direct} =
       CommonAPI.post(user_one, %{
-        "status" => "Hi @#{user_two.nickname}!",
+        "status" => "Hi @#{user_two.nickname}, @#{user_three.nickname}!",
         "visibility" => "direct"
       })
 
@@ -350,7 +448,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
              }
            ] = response
 
+    account_ids = Enum.map(res_accounts, & &1["id"])
     assert length(res_accounts) == 2
+    assert user_two.id in account_ids
+    assert user_three.id in account_ids
     assert is_binary(res_id)
     assert unread == true
     assert res_last_status["id"] == direct.id
@@ -443,7 +544,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
       |> assign(:user, user)
       |> get("/api/v1/accounts/verify_credentials")
 
-    assert %{"id" => id, "source" => %{"privacy" => "public"}} = json_response(conn, 200)
+    response = json_response(conn, 200)
+
+    assert %{"id" => id, "source" => %{"privacy" => "public"}} = response
+    assert response["pleroma"]["chat_token"]
     assert id == to_string(user.id)
   end
 
@@ -1417,6 +1521,82 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
     end
   end
 
+  describe "media upload" do
+    setup do
+      upload_config = Pleroma.Config.get([Pleroma.Upload])
+      proxy_config = Pleroma.Config.get([:media_proxy])
+
+      on_exit(fn ->
+        Pleroma.Config.put([Pleroma.Upload], upload_config)
+        Pleroma.Config.put([:media_proxy], proxy_config)
+      end)
+
+      user = insert(:user)
+
+      conn =
+        build_conn()
+        |> assign(:user, user)
+
+      image = %Plug.Upload{
+        content_type: "image/jpg",
+        path: Path.absname("test/fixtures/image.jpg"),
+        filename: "an_image.jpg"
+      }
+
+      [conn: conn, image: image]
+    end
+
+    test "returns uploaded image", %{conn: conn, image: image} do
+      desc = "Description of the image"
+
+      media =
+        conn
+        |> post("/api/v1/media", %{"file" => image, "description" => desc})
+        |> json_response(:ok)
+
+      assert media["type"] == "image"
+      assert media["description"] == desc
+      assert media["id"]
+
+      object = Repo.get(Object, media["id"])
+      assert object.data["actor"] == User.ap_id(conn.assigns[:user])
+    end
+
+    test "returns proxied url when media proxy is enabled", %{conn: conn, image: image} do
+      Pleroma.Config.put([Pleroma.Upload, :base_url], "https://media.pleroma.social")
+
+      proxy_url = "https://cache.pleroma.social"
+      Pleroma.Config.put([:media_proxy, :enabled], true)
+      Pleroma.Config.put([:media_proxy, :base_url], proxy_url)
+
+      media =
+        conn
+        |> post("/api/v1/media", %{"file" => image})
+        |> json_response(:ok)
+
+      assert String.starts_with?(media["url"], proxy_url)
+    end
+
+    test "returns media url when proxy is enabled but media url is whitelisted", %{
+      conn: conn,
+      image: image
+    } do
+      media_url = "https://media.pleroma.social"
+      Pleroma.Config.put([Pleroma.Upload, :base_url], media_url)
+
+      Pleroma.Config.put([:media_proxy, :enabled], true)
+      Pleroma.Config.put([:media_proxy, :base_url], "https://cache.pleroma.social")
+      Pleroma.Config.put([:media_proxy, :whitelist], ["media.pleroma.social"])
+
+      media =
+        conn
+        |> post("/api/v1/media", %{"file" => image})
+        |> json_response(:ok)
+
+      assert String.starts_with?(media["url"], media_url)
+    end
+  end
+
   describe "locked accounts" do
     test "/api/v1/follow_requests works" do
       user = insert(:user, %{info: %User.Info{locked: true}})
@@ -1526,32 +1706,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
     assert id == user.id
   end
 
-  test "media upload", %{conn: conn} do
-    file = %Plug.Upload{
-      content_type: "image/jpg",
-      path: Path.absname("test/fixtures/image.jpg"),
-      filename: "an_image.jpg"
-    }
-
-    desc = "Description of the image"
-
-    user = insert(:user)
-
-    conn =
-      conn
-      |> assign(:user, user)
-      |> post("/api/v1/media", %{"file" => file, "description" => desc})
-
-    assert media = json_response(conn, 200)
-
-    assert media["type"] == "image"
-    assert media["description"] == desc
-    assert media["id"]
-
-    object = Repo.get(Object, media["id"])
-    assert object.data["actor"] == User.ap_id(user)
-  end
-
   test "mascot upload", %{conn: conn} do
     user = insert(:user)
 
@@ -2080,104 +2234,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
     end)
   end
 
-  test "account search", %{conn: conn} do
-    user = insert(:user)
-    user_two = insert(:user, %{nickname: "shp@shitposter.club"})
-    user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
-
-    results =
-      conn
-      |> assign(:user, user)
-      |> get("/api/v1/accounts/search", %{"q" => "shp"})
-      |> json_response(200)
-
-    result_ids = for result <- results, do: result["acct"]
-
-    assert user_two.nickname in result_ids
-    assert user_three.nickname in result_ids
-
-    results =
-      conn
-      |> assign(:user, user)
-      |> get("/api/v1/accounts/search", %{"q" => "2hu"})
-      |> json_response(200)
-
-    result_ids = for result <- results, do: result["acct"]
-
-    assert user_three.nickname in result_ids
-  end
-
-  test "search", %{conn: conn} do
-    user = insert(:user)
-    user_two = insert(:user, %{nickname: "shp@shitposter.club"})
-    user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})
-
-    {:ok, activity} = CommonAPI.post(user, %{"status" => "This is about 2hu"})
-
-    {:ok, _activity} =
-      CommonAPI.post(user, %{
-        "status" => "This is about 2hu, but private",
-        "visibility" => "private"
-      })
-
-    {:ok, _} = CommonAPI.post(user_two, %{"status" => "This isn't"})
-
-    conn =
-      conn
-      |> get("/api/v1/search", %{"q" => "2hu"})
-
-    assert results = json_response(conn, 200)
-
-    [account | _] = results["accounts"]
-    assert account["id"] == to_string(user_three.id)
-
-    assert results["hashtags"] == []
-
-    [status] = results["statuses"]
-    assert status["id"] == to_string(activity.id)
-  end
-
-  test "search fetches remote statuses", %{conn: conn} do
-    capture_log(fn ->
-      conn =
-        conn
-        |> get("/api/v1/search", %{"q" => "https://shitposter.club/notice/2827873"})
-
-      assert results = json_response(conn, 200)
-
-      [status] = results["statuses"]
-      assert status["uri"] == "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
-    end)
-  end
-
-  test "search doesn't show statuses that it shouldn't", %{conn: conn} do
-    {:ok, activity} =
-      CommonAPI.post(insert(:user), %{
-        "status" => "This is about 2hu, but private",
-        "visibility" => "private"
-      })
-
-    capture_log(fn ->
-      conn =
-        conn
-        |> get("/api/v1/search", %{"q" => Object.normalize(activity).data["id"]})
-
-      assert results = json_response(conn, 200)
-
-      [] = results["statuses"]
-    end)
-  end
-
-  test "search fetches remote accounts", %{conn: conn} do
-    conn =
-      conn
-      |> get("/api/v1/search", %{"q" => "shp@social.heldscal.la", "resolve" => "true"})
-
-    assert results = json_response(conn, 200)
-    [account] = results["accounts"]
-    assert account["acct"] == "shp@social.heldscal.la"
-  end
-
   test "returns the favorites of a user", %{conn: conn} do
     user = insert(:user)
     other_user = insert(:user)
@@ -2418,205 +2474,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
     end
   end
 
-  describe "updating credentials" do
-    test "updates the user's bio", %{conn: conn} do
-      user = insert(:user)
-      user2 = insert(:user)
-
-      conn =
-        conn
-        |> assign(:user, user)
-        |> patch("/api/v1/accounts/update_credentials", %{
-          "note" => "I drink #cofe with @#{user2.nickname}"
-        })
-
-      assert user = json_response(conn, 200)
-
-      assert user["note"] ==
-               ~s(I drink <a class="hashtag" data-tag="cofe" href="http://localhost:4001/tag/cofe" rel="tag">#cofe</a> with <span class="h-card"><a data-user=") <>
-                 user2.id <>
-                 ~s(" class="u-url mention" href=") <>
-                 user2.ap_id <> ~s(">@<span>) <> user2.nickname <> ~s(</span></a></span>)
-    end
-
-    test "updates the user's locking status", %{conn: conn} do
-      user = insert(:user)
-
-      conn =
-        conn
-        |> assign(:user, user)
-        |> patch("/api/v1/accounts/update_credentials", %{locked: "true"})
-
-      assert user = json_response(conn, 200)
-      assert user["locked"] == true
-    end
-
-    test "updates the user's default scope", %{conn: conn} do
-      user = insert(:user)
-
-      conn =
-        conn
-        |> assign(:user, user)
-        |> patch("/api/v1/accounts/update_credentials", %{default_scope: "cofe"})
-
-      assert user = json_response(conn, 200)
-      assert user["source"]["privacy"] == "cofe"
-    end
-
-    test "updates the user's hide_followers status", %{conn: conn} do
-      user = insert(:user)
-
-      conn =
-        conn
-        |> assign(:user, user)
-        |> patch("/api/v1/accounts/update_credentials", %{hide_followers: "true"})
-
-      assert user = json_response(conn, 200)
-      assert user["pleroma"]["hide_followers"] == true
-    end
-
-    test "updates the user's hide_follows status", %{conn: conn} do
-      user = insert(:user)
-
-      conn =
-        conn
-        |> assign(:user, user)
-        |> patch("/api/v1/accounts/update_credentials", %{hide_follows: "true"})
-
-      assert user = json_response(conn, 200)
-      assert user["pleroma"]["hide_follows"] == true
-    end
-
-    test "updates the user's hide_favorites status", %{conn: conn} do
-      user = insert(:user)
-
-      conn =
-        conn
-        |> assign(:user, user)
-        |> patch("/api/v1/accounts/update_credentials", %{hide_favorites: "true"})
-
-      assert user = json_response(conn, 200)
-      assert user["pleroma"]["hide_favorites"] == true
-    end
-
-    test "updates the user's show_role status", %{conn: conn} do
-      user = insert(:user)
-
-      conn =
-        conn
-        |> assign(:user, user)
-        |> patch("/api/v1/accounts/update_credentials", %{show_role: "false"})
-
-      assert user = json_response(conn, 200)
-      assert user["source"]["pleroma"]["show_role"] == false
-    end
-
-    test "updates the user's no_rich_text status", %{conn: conn} do
-      user = insert(:user)
-
-      conn =
-        conn
-        |> assign(:user, user)
-        |> patch("/api/v1/accounts/update_credentials", %{no_rich_text: "true"})
-
-      assert user = json_response(conn, 200)
-      assert user["source"]["pleroma"]["no_rich_text"] == true
-    end
-
-    test "updates the user's name", %{conn: conn} do
-      user = insert(:user)
-
-      conn =
-        conn
-        |> assign(:user, user)
-        |> patch("/api/v1/accounts/update_credentials", %{"display_name" => "markorepairs"})
-
-      assert user = json_response(conn, 200)
-      assert user["display_name"] == "markorepairs"
-    end
-
-    test "updates the user's avatar", %{conn: conn} do
-      user = insert(:user)
-
-      new_avatar = %Plug.Upload{
-        content_type: "image/jpg",
-        path: Path.absname("test/fixtures/image.jpg"),
-        filename: "an_image.jpg"
-      }
-
-      conn =
-        conn
-        |> assign(:user, user)
-        |> patch("/api/v1/accounts/update_credentials", %{"avatar" => new_avatar})
-
-      assert user_response = json_response(conn, 200)
-      assert user_response["avatar"] != User.avatar_url(user)
-    end
-
-    test "updates the user's banner", %{conn: conn} do
-      user = insert(:user)
-
-      new_header = %Plug.Upload{
-        content_type: "image/jpg",
-        path: Path.absname("test/fixtures/image.jpg"),
-        filename: "an_image.jpg"
-      }
-
-      conn =
-        conn
-        |> assign(:user, user)
-        |> patch("/api/v1/accounts/update_credentials", %{"header" => new_header})
-
-      assert user_response = json_response(conn, 200)
-      assert user_response["header"] != User.banner_url(user)
-    end
-
-    test "requires 'write' permission", %{conn: conn} do
-      token1 = insert(:oauth_token, scopes: ["read"])
-      token2 = insert(:oauth_token, scopes: ["write", "follow"])
-
-      for token <- [token1, token2] do
-        conn =
-          conn
-          |> put_req_header("authorization", "Bearer #{token.token}")
-          |> patch("/api/v1/accounts/update_credentials", %{})
-
-        if token == token1 do
-          assert %{"error" => "Insufficient permissions: write."} == json_response(conn, 403)
-        else
-          assert json_response(conn, 200)
-        end
-      end
-    end
-
-    test "updates profile emojos", %{conn: conn} do
-      user = insert(:user)
-
-      note = "*sips :blank:*"
-      name = "I am :firefox:"
-
-      conn =
-        conn
-        |> assign(:user, user)
-        |> patch("/api/v1/accounts/update_credentials", %{
-          "note" => note,
-          "display_name" => name
-        })
-
-      assert json_response(conn, 200)
-
-      conn =
-        conn
-        |> get("/api/v1/accounts/#{user.id}")
-
-      assert user = json_response(conn, 200)
-
-      assert user["note"] == note
-      assert user["display_name"] == name
-      assert [%{"shortcode" => "blank"}, %{"shortcode" => "firefox"}] = user["emojis"]
-    end
-  end
-
   test "get instance information", %{conn: conn} do
     conn = get(conn, "/api/v1/instance")
     assert result = json_response(conn, 200)
@@ -2635,7 +2492,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
              "stats" => _,
              "thumbnail" => _,
              "languages" => _,
-             "registrations" => _
+             "registrations" => _,
+             "poll_limits" => _
            } = result
 
     assert email == from_config_email
@@ -2781,33 +2639,50 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
                |> post("/api/v1/statuses/#{activity_two.id}/pin")
                |> json_response(400)
     end
+  end
 
-    test "Status rich-media Card", %{conn: conn, user: user} do
+  describe "cards" do
+    setup do
       Pleroma.Config.put([:rich_media, :enabled], true)
+
+      on_exit(fn ->
+        Pleroma.Config.put([:rich_media, :enabled], false)
+      end)
+
+      user = insert(:user)
+      %{user: user}
+    end
+
+    test "returns rich-media card", %{conn: conn, user: user} do
       {:ok, activity} = CommonAPI.post(user, %{"status" => "http://example.com/ogp"})
 
+      card_data = %{
+        "image" => "http://ia.media-imdb.com/images/rock.jpg",
+        "provider_name" => "www.imdb.com",
+        "provider_url" => "http://www.imdb.com",
+        "title" => "The Rock",
+        "type" => "link",
+        "url" => "http://www.imdb.com/title/tt0117500/",
+        "description" =>
+          "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
+        "pleroma" => %{
+          "opengraph" => %{
+            "image" => "http://ia.media-imdb.com/images/rock.jpg",
+            "title" => "The Rock",
+            "type" => "video.movie",
+            "url" => "http://www.imdb.com/title/tt0117500/",
+            "description" =>
+              "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer."
+          }
+        }
+      }
+
       response =
         conn
         |> get("/api/v1/statuses/#{activity.id}/card")
         |> json_response(200)
 
-      assert response == %{
-               "image" => "http://ia.media-imdb.com/images/rock.jpg",
-               "provider_name" => "www.imdb.com",
-               "provider_url" => "http://www.imdb.com",
-               "title" => "The Rock",
-               "type" => "link",
-               "url" => "http://www.imdb.com/title/tt0117500/",
-               "description" => nil,
-               "pleroma" => %{
-                 "opengraph" => %{
-                   "image" => "http://ia.media-imdb.com/images/rock.jpg",
-                   "title" => "The Rock",
-                   "type" => "video.movie",
-                   "url" => "http://www.imdb.com/title/tt0117500/"
-                 }
-               }
-             }
+      assert response == card_data
 
       # works with private posts
       {:ok, activity} =
@@ -2819,9 +2694,33 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
         |> get("/api/v1/statuses/#{activity.id}/card")
         |> json_response(200)
 
-      assert response_two == response
+      assert response_two == card_data
+    end
+
+    test "replaces missing description with an empty string", %{conn: conn, user: user} do
+      {:ok, activity} = CommonAPI.post(user, %{"status" => "http://example.com/ogp-missing-data"})
 
-      Pleroma.Config.put([:rich_media, :enabled], false)
+      response =
+        conn
+        |> get("/api/v1/statuses/#{activity.id}/card")
+        |> json_response(:ok)
+
+      assert response == %{
+               "type" => "link",
+               "title" => "Pleroma",
+               "description" => "",
+               "image" => nil,
+               "provider_name" => "pleroma.social",
+               "provider_url" => "https://pleroma.social",
+               "url" => "https://pleroma.social/",
+               "pleroma" => %{
+                 "opengraph" => %{
+                   "title" => "Pleroma",
+                   "type" => "website",
+                   "url" => "https://pleroma.social/"
+                 }
+               }
+             }
     end
   end
 
@@ -2908,31 +2807,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
     end
   end
 
-  test "flavours switching (Pleroma Extension)", %{conn: conn} do
-    user = insert(:user)
-
-    get_old_flavour =
-      conn
-      |> assign(:user, user)
-      |> get("/api/v1/pleroma/flavour")
-
-    assert "glitch" == json_response(get_old_flavour, 200)
-
-    set_flavour =
-      conn
-      |> assign(:user, user)
-      |> post("/api/v1/pleroma/flavour/vanilla")
-
-    assert "vanilla" == json_response(set_flavour, 200)
-
-    get_new_flavour =
-      conn
-      |> assign(:user, user)
-      |> post("/api/v1/pleroma/flavour/vanilla")
-
-    assert json_response(set_flavour, 200) == json_response(get_new_flavour, 200)
-  end
-
   describe "reports" do
     setup do
       reporter = insert(:user)
@@ -3395,24 +3269,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
   end
 
   describe "create account by app" do
-    setup do
-      enabled = Pleroma.Config.get([:app_account_creation, :enabled])
-      max_requests = Pleroma.Config.get([:app_account_creation, :max_requests])
-      interval = Pleroma.Config.get([:app_account_creation, :interval])
-
-      Pleroma.Config.put([:app_account_creation, :enabled], true)
-      Pleroma.Config.put([:app_account_creation, :max_requests], 5)
-      Pleroma.Config.put([:app_account_creation, :interval], 1)
-
-      on_exit(fn ->
-        Pleroma.Config.put([:app_account_creation, :enabled], enabled)
-        Pleroma.Config.put([:app_account_creation, :max_requests], max_requests)
-        Pleroma.Config.put([:app_account_creation, :interval], interval)
-      end)
-
-      :ok
-    end
-
     test "Account registration via Application", %{conn: conn} do
       conn =
         conn
@@ -3515,7 +3371,127 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
           agreement: true
         })
 
-      assert json_response(conn, 403) == %{"error" => "Rate limit exceeded."}
+      assert json_response(conn, :too_many_requests) == %{"error" => "Throttled"}
+    end
+  end
+
+  describe "GET /api/v1/polls/:id" do
+    test "returns poll entity for object id", %{conn: conn} do
+      user = insert(:user)
+
+      {:ok, activity} =
+        CommonAPI.post(user, %{
+          "status" => "Pleroma does",
+          "poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20}
+        })
+
+      object = Object.normalize(activity)
+
+      conn =
+        conn
+        |> assign(:user, user)
+        |> get("/api/v1/polls/#{object.id}")
+
+      response = json_response(conn, 200)
+      id = object.id
+      assert %{"id" => ^id, "expired" => false, "multiple" => false} = response
+    end
+
+    test "does not expose polls for private statuses", %{conn: conn} do
+      user = insert(:user)
+      other_user = insert(:user)
+
+      {:ok, activity} =
+        CommonAPI.post(user, %{
+          "status" => "Pleroma does",
+          "poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20},
+          "visibility" => "private"
+        })
+
+      object = Object.normalize(activity)
+
+      conn =
+        conn
+        |> assign(:user, other_user)
+        |> get("/api/v1/polls/#{object.id}")
+
+      assert json_response(conn, 404)
+    end
+  end
+
+  describe "POST /api/v1/polls/:id/votes" do
+    test "votes are added to the poll", %{conn: conn} do
+      user = insert(:user)
+      other_user = insert(:user)
+
+      {:ok, activity} =
+        CommonAPI.post(user, %{
+          "status" => "A very delicious sandwich",
+          "poll" => %{
+            "options" => ["Lettuce", "Grilled Bacon", "Tomato"],
+            "expires_in" => 20,
+            "multiple" => true
+          }
+        })
+
+      object = Object.normalize(activity)
+
+      conn =
+        conn
+        |> assign(:user, other_user)
+        |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1, 2]})
+
+      assert json_response(conn, 200)
+      object = Object.get_by_id(object.id)
+
+      assert Enum.all?(object.data["anyOf"], fn %{"replies" => %{"totalItems" => total_items}} ->
+               total_items == 1
+             end)
+    end
+
+    test "author can't vote", %{conn: conn} do
+      user = insert(:user)
+
+      {:ok, activity} =
+        CommonAPI.post(user, %{
+          "status" => "Am I cute?",
+          "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20}
+        })
+
+      object = Object.normalize(activity)
+
+      assert conn
+             |> assign(:user, user)
+             |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [1]})
+             |> json_response(422) == %{"error" => "Poll's author can't vote"}
+
+      object = Object.get_by_id(object.id)
+
+      refute Enum.at(object.data["oneOf"], 1)["replies"]["totalItems"] == 1
+    end
+
+    test "does not allow multiple choices on a single-choice question", %{conn: conn} do
+      user = insert(:user)
+      other_user = insert(:user)
+
+      {:ok, activity} =
+        CommonAPI.post(user, %{
+          "status" => "The glass is",
+          "poll" => %{"options" => ["half empty", "half full"], "expires_in" => 20}
+        })
+
+      object = Object.normalize(activity)
+
+      assert conn
+             |> assign(:user, other_user)
+             |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1]})
+             |> json_response(422) == %{"error" => "Too many choices"}
+
+      object = Object.get_by_id(object.id)
+
+      refute Enum.any?(object.data["oneOf"], fn %{"replies" => %{"totalItems" => total_items}} ->
+               total_items == 1
+             end)
     end
   end
 end