Merge remote-tracking branch 'remotes/origin/develop' into automatic-authentication...
authorIvan Tashkinov <ivantashkinov@gmail.com>
Tue, 28 Apr 2020 16:56:20 +0000 (19:56 +0300)
committerIvan Tashkinov <ivantashkinov@gmail.com>
Tue, 28 Apr 2020 16:56:20 +0000 (19:56 +0300)
# Conflicts:
# lib/pleroma/web/mastodon_api/controllers/account_controller.ex

67 files changed:
CHANGELOG.md
benchmarks/load_testing/activities.ex
benchmarks/load_testing/fetcher.ex
benchmarks/mix/tasks/pleroma/load_testing.ex
docs/API/differences_in_mastoapi_responses.md
docs/configuration/hardening.md
lib/pleroma/plugs/http_security_plug.ex
lib/pleroma/stats.ex
lib/pleroma/user.ex
lib/pleroma/user/query.ex
lib/pleroma/web/activity_pub/activity_pub.ex
lib/pleroma/web/activity_pub/activity_pub_controller.ex
lib/pleroma/web/activity_pub/side_effects.ex
lib/pleroma/web/admin_api/views/status_view.ex
lib/pleroma/web/api_spec.ex
lib/pleroma/web/api_spec/helpers.ex
lib/pleroma/web/api_spec/operations/account_operation.ex [new file with mode: 0644]
lib/pleroma/web/api_spec/operations/app_operation.ex
lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex
lib/pleroma/web/api_spec/operations/domain_block_operation.ex
lib/pleroma/web/api_spec/render_error.ex [new file with mode: 0644]
lib/pleroma/web/api_spec/schemas/account.ex [new file with mode: 0644]
lib/pleroma/web/api_spec/schemas/account_field.ex [new file with mode: 0644]
lib/pleroma/web/api_spec/schemas/account_relationship.ex [new file with mode: 0644]
lib/pleroma/web/api_spec/schemas/actor_type.ex [new file with mode: 0644]
lib/pleroma/web/api_spec/schemas/api_error.ex [moved from lib/pleroma/web/api_spec/schemas/domain_block_request.ex with 56% similarity]
lib/pleroma/web/api_spec/schemas/app_create_request.ex [deleted file]
lib/pleroma/web/api_spec/schemas/app_create_response.ex [deleted file]
lib/pleroma/web/api_spec/schemas/boolean_like.ex [new file with mode: 0644]
lib/pleroma/web/api_spec/schemas/custom_emoji.ex [deleted file]
lib/pleroma/web/api_spec/schemas/custom_emojis_response.ex [deleted file]
lib/pleroma/web/api_spec/schemas/domain_blocks_response.ex [deleted file]
lib/pleroma/web/api_spec/schemas/emoji.ex [new file with mode: 0644]
lib/pleroma/web/api_spec/schemas/flake_id.ex [new file with mode: 0644]
lib/pleroma/web/api_spec/schemas/poll.ex [new file with mode: 0644]
lib/pleroma/web/api_spec/schemas/status.ex [new file with mode: 0644]
lib/pleroma/web/api_spec/schemas/visibility_scope.ex [new file with mode: 0644]
lib/pleroma/web/common_api/activity_draft.ex
lib/pleroma/web/common_api/common_api.ex
lib/pleroma/web/common_api/utils.ex
lib/pleroma/web/controller_helper.ex
lib/pleroma/web/mastodon_api/controllers/account_controller.ex
lib/pleroma/web/mastodon_api/controllers/status_controller.ex
lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
lib/pleroma/web/mastodon_api/views/status_view.ex
lib/pleroma/web/mongooseim/mongoose_im_controller.ex
lib/pleroma/web/oauth/scopes.ex
lib/pleroma/web/twitter_api/twitter_api.ex
mix.exs
mix.lock
test/stats_test.exs [moved from test/stat_test.exs with 86% similarity]
test/support/api_spec_helpers.ex [new file with mode: 0644]
test/support/conn_case.ex
test/web/activity_pub/activity_pub_test.exs
test/web/activity_pub/utils_test.exs
test/web/api_spec/app_operation_test.exs [deleted file]
test/web/api_spec/schema_examples_test.exs [new file with mode: 0644]
test/web/common_api/common_api_test.exs
test/web/common_api/common_api_utils_test.exs
test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
test/web/mastodon_api/controllers/account_controller_test.exs
test/web/mastodon_api/controllers/app_controller_test.exs
test/web/mastodon_api/controllers/custom_emoji_controller_test.exs
test/web/mastodon_api/controllers/domain_block_controller_test.exs
test/web/mastodon_api/controllers/status_controller_test.exs
test/web/mongooseim/mongoose_im_controller_test.exs
test/web/twitter_api/twitter_api_test.exs

index 702c581806e70f01be7df7da402961311c34fe37..ccc6a5bd4d3f2a9ad3026e09ec1e5b70d04db1f7 100644 (file)
@@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
   <summary>API Changes</summary>
 - Mastodon API: Support for `include_types` in `/api/v1/notifications`.
 - Mastodon API: Added `/api/v1/notifications/:id/dismiss` endpoint.
+- Mastodon API: Add support for filtering replies in public and home timelines
 - Admin API: endpoints for create/update/delete OAuth Apps.
 </details>
 
index 23ee2b987cd26258b4fa58b59e7493e622965596..482e42fc14d5841ef03d9a493140beaa02c65deb 100644 (file)
@@ -279,7 +279,7 @@ defmodule Pleroma.LoadTesting.Activities do
     actor = get_actor(group, user, friends, non_friends)
 
     with activity_id when not is_nil(activity_id) <- get_random_create_activity_id(),
-         {:ok, _activity, _object} <- CommonAPI.favorite(activity_id, actor) do
+         {:ok, _activity} <- CommonAPI.favorite(actor, activity_id) do
       :ok
     else
       {:error, _} ->
@@ -313,7 +313,7 @@ defmodule Pleroma.LoadTesting.Activities do
     tasks = get_reply_tasks(visibility, group)
 
     {:ok, activity} =
-      CommonAPI.post(user, %{"status" => "Simple status", "visibility" => "unlisted"})
+      CommonAPI.post(user, %{"status" => "Simple status", "visibility" => visibility})
 
     acc = {activity.id, ["@" <> actor.nickname, "reply to status"]}
     insert_replies(tasks, visibility, user, friends, non_friends, acc)
index 786929ace9faa1989e96e2896f554b7a6c533116..12c30f6f55a1b71f88070a83b7106b7667b4da43 100644 (file)
@@ -41,6 +41,7 @@ defmodule Pleroma.LoadTesting.Fetcher do
     fetch_notifications(user)
     fetch_favourites(user)
     fetch_long_thread(user)
+    fetch_timelines_with_reply_filtering(user)
   end
 
   defp render_views(user) do
@@ -495,4 +496,58 @@ defmodule Pleroma.LoadTesting.Fetcher do
       formatters: formatters()
     )
   end
+
+  defp fetch_timelines_with_reply_filtering(user) do
+    public_params = opts_for_public_timeline(user)
+
+    Benchee.run(
+      %{
+        "Public timeline without reply filtering" => fn ->
+          ActivityPub.fetch_public_activities(public_params)
+        end,
+        "Public timeline with reply filtering - following" => fn ->
+          public_params
+          |> Map.put("reply_visibility", "following")
+          |> Map.put("reply_filtering_user", user)
+          |> ActivityPub.fetch_public_activities()
+        end,
+        "Public timeline with reply filtering - self" => fn ->
+          public_params
+          |> Map.put("reply_visibility", "self")
+          |> Map.put("reply_filtering_user", user)
+          |> ActivityPub.fetch_public_activities()
+        end
+      },
+      formatters: formatters()
+    )
+
+    private_params = opts_for_home_timeline(user)
+
+    recipients = [user.ap_id | User.following(user)]
+
+    Benchee.run(
+      %{
+        "Home timeline without reply filtering" => fn ->
+          ActivityPub.fetch_activities(recipients, private_params)
+        end,
+        "Home timeline with reply filtering - following" => fn ->
+          private_params =
+            private_params
+            |> Map.put("reply_filtering_user", user)
+            |> Map.put("reply_visibility", "following")
+
+          ActivityPub.fetch_activities(recipients, private_params)
+        end,
+        "Home timeline with reply filtering - self" => fn ->
+          private_params =
+            private_params
+            |> Map.put("reply_filtering_user", user)
+            |> Map.put("reply_visibility", "self")
+
+          ActivityPub.fetch_activities(recipients, private_params)
+        end
+      },
+      formatters: formatters()
+    )
+  end
 end
index 72b225f09e330f872c99eca21d90db4e2d5a2a28..3888832402230a57cdb7adbf8bcf461c71be5be0 100644 (file)
@@ -44,6 +44,7 @@ defmodule Mix.Tasks.Pleroma.LoadTesting do
   ]
 
   def run(args) do
+    Logger.configure(level: :error)
     Mix.Pleroma.start_pleroma()
     clean_tables()
     {opts, _} = OptionParser.parse!(args, strict: @switches, aliases: @aliases)
index 1059155cfccba73ec292a048f7fd5c13fbd3f9ed..289f859306fe229b96b0a5fb8989577ca78d49eb 100644 (file)
@@ -4,7 +4,7 @@ A Pleroma instance can be identified by "<Mastodon version> (compatible; Pleroma
 
 ## Flake IDs
 
-Pleroma uses 128-bit ids as opposed to Mastodon's 64 bits. However just like Mastodon's ids they are sortable strings
+Pleroma uses 128-bit ids as opposed to Mastodon's 64 bits. However just like Mastodon's ids they are lexically sortable strings
 
 ## Attachment cap
 
@@ -14,6 +14,7 @@ Some apps operate under the assumption that no more than 4 attachments can be re
 
 Adding the parameter `with_muted=true` to the timeline queries will also return activities by muted (not by blocked!) users.
 Adding the parameter `exclude_visibilities` to the timeline queries will exclude the statuses with the given visibilities. The parameter accepts an array of visibility types (`public`, `unlisted`, `private`, `direct`), e.g., `exclude_visibilities[]=direct&exclude_visibilities[]=private`.
+Adding the parameter `reply_visibility` to the public and home timelines queries will filter replies. Possible values: without parameter (default) shows all replies, `following` - replies directed to you or users you follow, `self` - replies directed to you.
 
 ## Statuses
 
@@ -119,6 +120,18 @@ Accepts additional parameters:
 - `exclude_visibilities`: will exclude the notifications for activities with the given visibilities. The parameter accepts an array of visibility types (`public`, `unlisted`, `private`, `direct`). Usage example: `GET /api/v1/notifications?exclude_visibilities[]=direct&exclude_visibilities[]=private`.
 - `include_types`: will include the notifications for activities with the given types. The parameter accepts an array of types (`mention`, `follow`, `reblog`, `favourite`, `move`, `pleroma:emoji_reaction`). Usage example: `GET /api/v1/notifications?include_types[]=mention&include_types[]=reblog`.
 
+## DELETE `/api/v1/notifications/destroy_multiple`
+
+An endpoint to delete multiple statuses by IDs.
+
+Required parameters:
+
+- `ids`: array of activity ids
+
+Usage example: `DELETE /api/v1/notifications/destroy_multiple/?ids[]=1&ids[]=2`.
+
+Returns on success: 200 OK `{}`
+
 ## POST `/api/v1/statuses`
 
 Additional parameters can be added to the JSON body/Form data:
index b54c28850d04e93d2e245e9bca3263ed20558a3a..d3bfc4e4a692abb34f8fbc038136d876d39ae186 100644 (file)
@@ -36,7 +36,7 @@ content-security-policy:
   default-src 'none';
   base-uri 'self';
   frame-ancestors 'none';
-  img-src 'self' data: https:;
+  img-src 'self' data: blob: https:;
   media-src 'self' https:;
   style-src 'self' 'unsafe-inline';
   font-src 'self';
index 81e6b4f2a36c04bc3b693582384c46090be11bc8..6462797b635787d39160b192c80d857e462c1482 100644 (file)
@@ -75,7 +75,7 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
       "default-src 'none'",
       "base-uri 'self'",
       "frame-ancestors 'none'",
-      "img-src 'self' data: https:",
+      "img-src 'self' data: blob: https:",
       "media-src 'self' https:",
       "style-src 'self' 'unsafe-inline'",
       "font-src 'self'",
index 4446562ac0b797fc28762cee480387796852b28d..8d2809bbbe546bce9399f069d72f11027da5ba45 100644 (file)
@@ -45,11 +45,11 @@ defmodule Pleroma.Stats do
   end
 
   def init(_args) do
-    {:ok, get_stat_data()}
+    {:ok, calculate_stat_data()}
   end
 
   def handle_call(:force_update, _from, _state) do
-    new_stats = get_stat_data()
+    new_stats = calculate_stat_data()
     {:reply, new_stats, new_stats}
   end
 
@@ -58,12 +58,12 @@ defmodule Pleroma.Stats do
   end
 
   def handle_cast(:run_update, _state) do
-    new_stats = get_stat_data()
+    new_stats = calculate_stat_data()
 
     {:noreply, new_stats}
   end
 
-  defp get_stat_data do
+  def calculate_stat_data do
     peers =
       from(
         u in User,
@@ -77,7 +77,15 @@ defmodule Pleroma.Stats do
 
     status_count = Repo.aggregate(User.Query.build(%{local: true}), :sum, :note_count)
 
-    user_count = Repo.aggregate(User.Query.build(%{local: true, active: true}), :count, :id)
+    users_query =
+      from(u in User,
+        where: u.deactivated != true,
+        where: u.local == true,
+        where: not is_nil(u.nickname),
+        where: not u.invisible
+      )
+
+    user_count = Repo.aggregate(users_query, :count, :id)
 
     %{
       peers: peers,
index 477237756b0449105eada1726835ccfea6f3c9bb..b451202b255209cee0672c9ffd72e26c982ef5b7 100644 (file)
@@ -832,6 +832,7 @@ defmodule Pleroma.User do
   def set_cache(%User{} = user) do
     Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)
     Cachex.put(:user_cache, "nickname:#{user.nickname}", user)
+    Cachex.put(:user_cache, "friends_ap_ids:#{user.nickname}", get_user_friends_ap_ids(user))
     {:ok, user}
   end
 
@@ -847,9 +848,22 @@ defmodule Pleroma.User do
     end
   end
 
+  def get_user_friends_ap_ids(user) do
+    from(u in User.get_friends_query(user), select: u.ap_id)
+    |> Repo.all()
+  end
+
+  @spec get_cached_user_friends_ap_ids(User.t()) :: [String.t()]
+  def get_cached_user_friends_ap_ids(user) do
+    Cachex.fetch!(:user_cache, "friends_ap_ids:#{user.ap_id}", fn _ ->
+      get_user_friends_ap_ids(user)
+    end)
+  end
+
   def invalidate_cache(user) do
     Cachex.del(:user_cache, "ap_id:#{user.ap_id}")
     Cachex.del(:user_cache, "nickname:#{user.nickname}")
+    Cachex.del(:user_cache, "friends_ap_ids:#{user.ap_id}")
   end
 
   @spec get_cached_by_ap_id(String.t()) :: User.t() | nil
index ec88088cf7459e8bf35a99b718bde76308de7e9c..ac77aab7134769f4b525c0df9ca4f9304bd9dc1a 100644 (file)
@@ -54,13 +54,13 @@ defmodule Pleroma.User.Query do
             select: term(),
             limit: pos_integer()
           }
-          | %{}
+          | map()
 
   @ilike_criteria [:nickname, :name, :query]
   @equal_criteria [:email]
   @contains_criteria [:ap_id, :nickname]
 
-  @spec build(criteria()) :: Query.t()
+  @spec build(Query.t(), criteria()) :: Query.t()
   def build(query \\ base_query(), criteria) do
     prepare_query(query, criteria)
   end
index 4a133498e974ddb10a59beea9db8189109ce7584..1f4a093702053b1a82e5adc66328b5d4939db728 100644 (file)
@@ -398,36 +398,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     end
   end
 
-  # TODO: This is weird, maybe we shouldn't check here if we can make the activity.
-  @spec like(User.t(), Object.t(), String.t() | nil, boolean()) ::
-          {:ok, Activity.t(), Object.t()} | {:error, any()}
-  def like(user, object, activity_id \\ nil, local \\ true) do
-    with {:ok, result} <- Repo.transaction(fn -> do_like(user, object, activity_id, local) end) do
-      result
-    end
-  end
-
-  defp do_like(
-         %User{ap_id: ap_id} = user,
-         %Object{data: %{"id" => _}} = object,
-         activity_id,
-         local
-       ) do
-    with nil <- get_existing_like(ap_id, object),
-         like_data <- make_like_data(user, object, activity_id),
-         {:ok, activity} <- insert(like_data, local),
-         {:ok, object} <- add_like_to_object(activity, object),
-         :ok <- maybe_federate(activity) do
-      {:ok, activity, object}
-    else
-      %Activity{} = activity ->
-        {:ok, activity, object}
-
-      {:error, error} ->
-        Repo.rollback(error)
-    end
-  end
-
   @spec unlike(User.t(), Object.t(), String.t() | nil, boolean()) ::
           {:ok, Activity.t(), Activity.t(), Object.t()} | {:ok, Object.t()} | {:error, any()}
   def unlike(%User{} = actor, %Object{} = object, activity_id \\ nil, local \\ true) do
@@ -468,6 +438,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   defp do_announce(user, object, activity_id, local, public) do
     with true <- is_announceable?(object, user, public),
+         object <- Object.get_by_id(object.id),
          announce_data <- make_announce_data(user, object, activity_id, public),
          {:ok, activity} <- insert(announce_data, local),
          {:ok, object} <- add_announce_to_object(activity, object),
@@ -854,7 +825,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
   end
 
   defp exclude_visibility(query, %{"exclude_visibilities" => visibility})
-       when visibility not in @valid_visibilities do
+       when visibility not in [nil | @valid_visibilities] do
     Logger.error("Could not exclude visibility to #{visibility}")
     query
   end
@@ -1061,7 +1032,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     raise "Can't use the child object without preloading!"
   end
 
-  defp restrict_media(query, %{"only_media" => val}) when val == "true" or val == "1" do
+  defp restrict_media(query, %{"only_media" => val}) when val in [true, "true", "1"] do
     from(
       [_activity, object] in query,
       where: fragment("not (?)->'attachment' = (?)", object.data, ^[])
@@ -1070,16 +1041,51 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   defp restrict_media(query, _), do: query
 
-  defp restrict_replies(query, %{"exclude_replies" => val}) when val == "true" or val == "1" do
+  defp restrict_replies(query, %{"exclude_replies" => val}) when val in [true, "true", "1"] do
     from(
       [_activity, object] in query,
       where: fragment("?->>'inReplyTo' is null", object.data)
     )
   end
 
+  defp restrict_replies(query, %{
+         "reply_filtering_user" => user,
+         "reply_visibility" => "self"
+       }) do
+    from(
+      [activity, object] in query,
+      where:
+        fragment(
+          "?->>'inReplyTo' is null OR ? = ANY(?)",
+          object.data,
+          ^user.ap_id,
+          activity.recipients
+        )
+    )
+  end
+
+  defp restrict_replies(query, %{
+         "reply_filtering_user" => user,
+         "reply_visibility" => "following"
+       }) do
+    from(
+      [activity, object] in query,
+      where:
+        fragment(
+          "?->>'inReplyTo' is null OR ? && array_remove(?, ?) OR ? = ?",
+          object.data,
+          ^[user.ap_id | User.get_cached_user_friends_ap_ids(user)],
+          activity.recipients,
+          activity.actor,
+          activity.actor,
+          ^user.ap_id
+        )
+    )
+  end
+
   defp restrict_replies(query, _), do: query
 
-  defp restrict_reblogs(query, %{"exclude_reblogs" => val}) when val == "true" or val == "1" do
+  defp restrict_reblogs(query, %{"exclude_reblogs" => val}) when val in [true, "true", "1"] do
     from(activity in query, where: fragment("?->>'type' != 'Announce'", activity.data))
   end
 
@@ -1158,7 +1164,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     )
   end
 
-  defp restrict_pinned(query, %{"pinned" => "true", "pinned_activity_ids" => ids}) do
+  # TODO: when all endpoints migrated to OpenAPI compare `pinned` with `true` (boolean) only,
+  # the same for `restrict_media/2`, `restrict_replies/2`, 'restrict_reblogs/2'
+  # and `restrict_muted/2`
+
+  defp restrict_pinned(query, %{"pinned" => pinned, "pinned_activity_ids" => ids})
+       when pinned in [true, "true", "1"] do
     from(activity in query, where: activity.id in ^ids)
   end
 
@@ -1291,6 +1302,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     |> maybe_set_thread_muted_field(opts)
     |> maybe_order(opts)
     |> restrict_recipients(recipients, opts["user"])
+    |> restrict_replies(opts)
     |> restrict_tag(opts)
     |> restrict_tag_reject(opts)
     |> restrict_tag_all(opts)
@@ -1305,7 +1317,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     |> restrict_media(opts)
     |> restrict_visibility(opts)
     |> restrict_thread_visibility(opts, config)
-    |> restrict_replies(opts)
     |> restrict_reblogs(opts)
     |> restrict_pinned(opts)
     |> restrict_muted_reblogs(restrict_muted_reblogs_opts)
index 8b9eb4a2c718598d372c5ca6c288f75ac50f808f..d625530ecb575586986e22735544299733fe498a 100644 (file)
@@ -12,8 +12,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
   alias Pleroma.Plugs.EnsureAuthenticatedPlug
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ActivityPub
+  alias Pleroma.Web.ActivityPub.Builder
   alias Pleroma.Web.ActivityPub.InternalFetchActor
   alias Pleroma.Web.ActivityPub.ObjectView
+  alias Pleroma.Web.ActivityPub.Pipeline
   alias Pleroma.Web.ActivityPub.Relay
   alias Pleroma.Web.ActivityPub.Transmogrifier
   alias Pleroma.Web.ActivityPub.UserView
@@ -421,7 +423,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
 
   defp handle_user_activity(%User{} = user, %{"type" => "Like"} = params) do
     with %Object{} = object <- Object.normalize(params["object"]),
-         {:ok, activity, _object} <- ActivityPub.like(user, object) do
+         {_, {:ok, like_object, meta}} <- {:build_object, Builder.like(user, object)},
+         {_, {:ok, %Activity{} = activity, _meta}} <-
+           {:common_pipeline,
+            Pipeline.common_pipeline(like_object, Keyword.put(meta, :local, true))} do
       {:ok, activity}
     else
       _ -> {:error, dgettext("errors", "Can't like object")}
index 6a8f1af960ffe34aadb35b287d2c0e6d38c9365e..5981e754567e0c098340a1c77395b4402c87dc1f 100644 (file)
@@ -15,12 +15,17 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
   # - Add like to object
   # - Set up notification
   def handle(%{data: %{"type" => "Like"}} = object, meta) do
-    liked_object = Object.get_by_ap_id(object.data["object"])
-    Utils.add_like_to_object(object, liked_object)
+    {:ok, result} =
+      Pleroma.Repo.transaction(fn ->
+        liked_object = Object.get_by_ap_id(object.data["object"])
+        Utils.add_like_to_object(object, liked_object)
 
-    Notification.create_notifications(object)
+        Notification.create_notifications(object)
 
-    {:ok, object, meta}
+        {:ok, object, meta}
+      end)
+
+    result
   end
 
   # Nothing to do
index 360ddc22ccf12cdbc6995b465c7c5775fb4ac815..3637dee24eb42fef069b9307bd5ad04a40bf3b87 100644 (file)
@@ -8,15 +8,16 @@ defmodule Pleroma.Web.AdminAPI.StatusView do
   require Pleroma.Constants
 
   alias Pleroma.User
+  alias Pleroma.Web.MastodonAPI.StatusView
 
   def render("index.json", opts) do
     safe_render_many(opts.activities, __MODULE__, "show.json", opts)
   end
 
   def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do
-    user = get_user(activity.data["actor"])
+    user = StatusView.get_user(activity.data["actor"])
 
-    Pleroma.Web.MastodonAPI.StatusView.render("show.json", opts)
+    StatusView.render("show.json", opts)
     |> Map.merge(%{account: merge_account_views(user)})
   end
 
@@ -26,17 +27,4 @@ defmodule Pleroma.Web.AdminAPI.StatusView do
   end
 
   defp merge_account_views(_), do: %{}
-
-  defp get_user(ap_id) do
-    cond do
-      user = User.get_cached_by_ap_id(ap_id) ->
-        user
-
-      user = User.get_by_guessed_nickname(ap_id) ->
-        user
-
-      true ->
-        User.error_user(ap_id)
-    end
-  end
 end
index 3890489e36b6b302127a1607320969b3854c03b7..b3c1e3ea24e7a403b4202893e0f96a3330f0b561 100644 (file)
@@ -4,6 +4,7 @@
 
 defmodule Pleroma.Web.ApiSpec do
   alias OpenApiSpex.OpenApi
+  alias OpenApiSpex.Operation
   alias Pleroma.Web.Endpoint
   alias Pleroma.Web.Router
 
@@ -24,6 +25,13 @@ defmodule Pleroma.Web.ApiSpec do
       # populate the paths from a phoenix router
       paths: OpenApiSpex.Paths.from_router(Router),
       components: %OpenApiSpex.Components{
+        parameters: %{
+          "accountIdOrNickname" =>
+            Operation.parameter(:id, :path, :string, "Account ID or nickname",
+              example: "123",
+              required: true
+            )
+        },
         securitySchemes: %{
           "oAuth" => %OpenApiSpex.SecurityScheme{
             type: "oauth2",
index 7348dcbee6c5d32a667f7e6a9a1e565627d486d5..ce40fb9e800575063ea7b81db416679a5efc7fa7 100644 (file)
@@ -3,6 +3,9 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.ApiSpec.Helpers do
+  alias OpenApiSpex.Operation
+  alias OpenApiSpex.Schema
+
   def request_body(description, schema_ref, opts \\ []) do
     media_types = ["application/json", "multipart/form-data", "application/x-www-form-urlencoded"]
 
@@ -24,4 +27,23 @@ defmodule Pleroma.Web.ApiSpec.Helpers do
       required: opts[:required] || false
     }
   end
+
+  def pagination_params do
+    [
+      Operation.parameter(:max_id, :query, :string, "Return items older than this ID"),
+      Operation.parameter(:min_id, :query, :string, "Return the oldest items newer than this ID"),
+      Operation.parameter(
+        :since_id,
+        :query,
+        :string,
+        "Return the newest items newer than this ID"
+      ),
+      Operation.parameter(
+        :limit,
+        :query,
+        %Schema{type: :integer, default: 20, maximum: 40},
+        "Limit"
+      )
+    ]
+  end
 end
diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
new file mode 100644 (file)
index 0000000..2efe6e9
--- /dev/null
@@ -0,0 +1,701 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.AccountOperation do
+  alias OpenApiSpex.Operation
+  alias OpenApiSpex.Reference
+  alias OpenApiSpex.Schema
+  alias Pleroma.Web.ApiSpec.Schemas.Account
+  alias Pleroma.Web.ApiSpec.Schemas.AccountRelationship
+  alias Pleroma.Web.ApiSpec.Schemas.ActorType
+  alias Pleroma.Web.ApiSpec.Schemas.ApiError
+  alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
+  alias Pleroma.Web.ApiSpec.Schemas.Status
+  alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
+
+  import Pleroma.Web.ApiSpec.Helpers
+
+  @spec open_api_operation(atom) :: Operation.t()
+  def open_api_operation(action) do
+    operation = String.to_existing_atom("#{action}_operation")
+    apply(__MODULE__, operation, [])
+  end
+
+  @spec create_operation() :: Operation.t()
+  def create_operation do
+    %Operation{
+      tags: ["accounts"],
+      summary: "Register an account",
+      description:
+        "Creates a user and account records. Returns an account access token for the app that initiated the request. The app should save this token for later, and should wait for the user to confirm their account by clicking a link in their email inbox.",
+      operationId: "AccountController.create",
+      requestBody: request_body("Parameters", create_request(), required: true),
+      responses: %{
+        200 => Operation.response("Account", "application/json", create_response()),
+        400 => Operation.response("Error", "application/json", ApiError),
+        403 => Operation.response("Error", "application/json", ApiError),
+        429 => Operation.response("Error", "application/json", ApiError)
+      }
+    }
+  end
+
+  def verify_credentials_operation do
+    %Operation{
+      tags: ["accounts"],
+      description: "Test to make sure that the user token works.",
+      summary: "Verify account credentials",
+      operationId: "AccountController.verify_credentials",
+      security: [%{"oAuth" => ["read:accounts"]}],
+      responses: %{
+        200 => Operation.response("Account", "application/json", Account)
+      }
+    }
+  end
+
+  def update_credentials_operation do
+    %Operation{
+      tags: ["accounts"],
+      summary: "Update account credentials",
+      description: "Update the user's display and preferences.",
+      operationId: "AccountController.update_credentials",
+      security: [%{"oAuth" => ["write:accounts"]}],
+      requestBody: request_body("Parameters", update_creadentials_request(), required: true),
+      responses: %{
+        200 => Operation.response("Account", "application/json", Account),
+        403 => Operation.response("Error", "application/json", ApiError)
+      }
+    }
+  end
+
+  def relationships_operation do
+    %Operation{
+      tags: ["accounts"],
+      summary: "Check relationships to other accounts",
+      operationId: "AccountController.relationships",
+      description: "Find out whether a given account is followed, blocked, muted, etc.",
+      security: [%{"oAuth" => ["read:follows"]}],
+      parameters: [
+        Operation.parameter(
+          :id,
+          :query,
+          %Schema{
+            oneOf: [%Schema{type: :array, items: %Schema{type: :string}}, %Schema{type: :string}]
+          },
+          "Account IDs",
+          example: "123"
+        )
+      ],
+      responses: %{
+        200 => Operation.response("Account", "application/json", array_of_relationships())
+      }
+    }
+  end
+
+  def show_operation do
+    %Operation{
+      tags: ["accounts"],
+      summary: "Account",
+      operationId: "AccountController.show",
+      description: "View information about a profile.",
+      parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
+      responses: %{
+        200 => Operation.response("Account", "application/json", Account),
+        404 => Operation.response("Error", "application/json", ApiError)
+      }
+    }
+  end
+
+  def statuses_operation do
+    %Operation{
+      tags: ["accounts"],
+      summary: "Statuses",
+      operationId: "AccountController.statuses",
+      description:
+        "Statuses posted to the given account. Public (for public statuses only), or user token + `read:statuses` (for private statuses the user is authorized to see)",
+      parameters:
+        [
+          %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
+          Operation.parameter(:pinned, :query, BooleanLike, "Include only pinned statuses"),
+          Operation.parameter(:tagged, :query, :string, "With tag"),
+          Operation.parameter(
+            :only_media,
+            :query,
+            BooleanLike,
+            "Include only statuses with media attached"
+          ),
+          Operation.parameter(
+            :with_muted,
+            :query,
+            BooleanLike,
+            "Include statuses from muted acccounts."
+          ),
+          Operation.parameter(:exclude_reblogs, :query, BooleanLike, "Exclude reblogs"),
+          Operation.parameter(
+            :exclude_visibilities,
+            :query,
+            %Schema{type: :array, items: VisibilityScope},
+            "Exclude visibilities"
+          )
+        ] ++ pagination_params(),
+      responses: %{
+        200 => Operation.response("Statuses", "application/json", array_of_statuses()),
+        404 => Operation.response("Error", "application/json", ApiError)
+      }
+    }
+  end
+
+  def followers_operation do
+    %Operation{
+      tags: ["accounts"],
+      summary: "Followers",
+      operationId: "AccountController.followers",
+      security: [%{"oAuth" => ["read:accounts"]}],
+      description:
+        "Accounts which follow the given account, if network is not hidden by the account owner.",
+      parameters:
+        [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}] ++ pagination_params(),
+      responses: %{
+        200 => Operation.response("Accounts", "application/json", array_of_accounts())
+      }
+    }
+  end
+
+  def following_operation do
+    %Operation{
+      tags: ["accounts"],
+      summary: "Following",
+      operationId: "AccountController.following",
+      security: [%{"oAuth" => ["read:accounts"]}],
+      description:
+        "Accounts which the given account is following, if network is not hidden by the account owner.",
+      parameters:
+        [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}] ++ pagination_params(),
+      responses: %{200 => Operation.response("Accounts", "application/json", array_of_accounts())}
+    }
+  end
+
+  def lists_operation do
+    %Operation{
+      tags: ["accounts"],
+      summary: "Lists containing this account",
+      operationId: "AccountController.lists",
+      security: [%{"oAuth" => ["read:lists"]}],
+      description: "User lists that you have added this account to.",
+      parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
+      responses: %{200 => Operation.response("Lists", "application/json", array_of_lists())}
+    }
+  end
+
+  def follow_operation do
+    %Operation{
+      tags: ["accounts"],
+      summary: "Follow",
+      operationId: "AccountController.follow",
+      security: [%{"oAuth" => ["follow", "write:follows"]}],
+      description: "Follow the given account",
+      parameters: [
+        %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
+        Operation.parameter(
+          :reblogs,
+          :query,
+          BooleanLike,
+          "Receive this account's reblogs in home timeline? Defaults to true."
+        )
+      ],
+      responses: %{
+        200 => Operation.response("Relationship", "application/json", AccountRelationship),
+        400 => Operation.response("Error", "application/json", ApiError),
+        404 => Operation.response("Error", "application/json", ApiError)
+      }
+    }
+  end
+
+  def unfollow_operation do
+    %Operation{
+      tags: ["accounts"],
+      summary: "Unfollow",
+      operationId: "AccountController.unfollow",
+      security: [%{"oAuth" => ["follow", "write:follows"]}],
+      description: "Unfollow the given account",
+      parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
+      responses: %{
+        200 => Operation.response("Relationship", "application/json", AccountRelationship),
+        400 => Operation.response("Error", "application/json", ApiError),
+        404 => Operation.response("Error", "application/json", ApiError)
+      }
+    }
+  end
+
+  def mute_operation do
+    %Operation{
+      tags: ["accounts"],
+      summary: "Mute",
+      operationId: "AccountController.mute",
+      security: [%{"oAuth" => ["follow", "write:mutes"]}],
+      requestBody: request_body("Parameters", mute_request()),
+      description:
+        "Mute the given account. Clients should filter statuses and notifications from this account, if received (e.g. due to a boost in the Home timeline).",
+      parameters: [
+        %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
+        Operation.parameter(
+          :notifications,
+          :query,
+          %Schema{allOf: [BooleanLike], default: true},
+          "Mute notifications in addition to statuses? Defaults to `true`."
+        )
+      ],
+      responses: %{
+        200 => Operation.response("Relationship", "application/json", AccountRelationship)
+      }
+    }
+  end
+
+  def unmute_operation do
+    %Operation{
+      tags: ["accounts"],
+      summary: "Unmute",
+      operationId: "AccountController.unmute",
+      security: [%{"oAuth" => ["follow", "write:mutes"]}],
+      description: "Unmute the given account.",
+      parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
+      responses: %{
+        200 => Operation.response("Relationship", "application/json", AccountRelationship)
+      }
+    }
+  end
+
+  def block_operation do
+    %Operation{
+      tags: ["accounts"],
+      summary: "Block",
+      operationId: "AccountController.block",
+      security: [%{"oAuth" => ["follow", "write:blocks"]}],
+      description:
+        "Block the given account. Clients should filter statuses from this account if received (e.g. due to a boost in the Home timeline)",
+      parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
+      responses: %{
+        200 => Operation.response("Relationship", "application/json", AccountRelationship)
+      }
+    }
+  end
+
+  def unblock_operation do
+    %Operation{
+      tags: ["accounts"],
+      summary: "Unblock",
+      operationId: "AccountController.unblock",
+      security: [%{"oAuth" => ["follow", "write:blocks"]}],
+      description: "Unblock the given account.",
+      parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
+      responses: %{
+        200 => Operation.response("Relationship", "application/json", AccountRelationship)
+      }
+    }
+  end
+
+  def follows_operation do
+    %Operation{
+      tags: ["accounts"],
+      summary: "Follows",
+      operationId: "AccountController.follows",
+      security: [%{"oAuth" => ["follow", "write:follows"]}],
+      requestBody: request_body("Parameters", follows_request(), required: true),
+      responses: %{
+        200 => Operation.response("Account", "application/json", AccountRelationship),
+        400 => Operation.response("Error", "application/json", ApiError),
+        404 => Operation.response("Error", "application/json", ApiError)
+      }
+    }
+  end
+
+  def mutes_operation do
+    %Operation{
+      tags: ["accounts"],
+      summary: "Muted accounts",
+      operationId: "AccountController.mutes",
+      description: "Accounts the user has muted.",
+      security: [%{"oAuth" => ["follow", "read:mutes"]}],
+      responses: %{
+        200 => Operation.response("Accounts", "application/json", array_of_accounts())
+      }
+    }
+  end
+
+  def blocks_operation do
+    %Operation{
+      tags: ["accounts"],
+      summary: "Blocked users",
+      operationId: "AccountController.blocks",
+      description: "View your blocks. See also accounts/:id/{block,unblock}",
+      security: [%{"oAuth" => ["read:blocks"]}],
+      responses: %{
+        200 => Operation.response("Accounts", "application/json", array_of_accounts())
+      }
+    }
+  end
+
+  def endorsements_operation do
+    %Operation{
+      tags: ["accounts"],
+      summary: "Endorsements",
+      operationId: "AccountController.endorsements",
+      description: "Not implemented",
+      security: [%{"oAuth" => ["read:accounts"]}],
+      responses: %{
+        200 => Operation.response("Empry array", "application/json", %Schema{type: :array})
+      }
+    }
+  end
+
+  def identity_proofs_operation do
+    %Operation{
+      tags: ["accounts"],
+      summary: "Identity proofs",
+      operationId: "AccountController.identity_proofs",
+      description: "Not implemented",
+      responses: %{
+        200 => Operation.response("Empry array", "application/json", %Schema{type: :array})
+      }
+    }
+  end
+
+  defp create_request do
+    %Schema{
+      title: "AccountCreateRequest",
+      description: "POST body for creating an account",
+      type: :object,
+      properties: %{
+        reason: %Schema{
+          type: :string,
+          description:
+            "Text that will be reviewed by moderators if registrations require manual approval"
+        },
+        username: %Schema{type: :string, description: "The desired username for the account"},
+        email: %Schema{
+          type: :string,
+          description:
+            "The email address to be used for login. Required when `account_activation_required` is enabled.",
+          format: :email
+        },
+        password: %Schema{
+          type: :string,
+          description: "The password to be used for login",
+          format: :password
+        },
+        agreement: %Schema{
+          type: :boolean,
+          description:
+            "Whether the user agrees to the local rules, terms, and policies. These should be presented to the user in order to allow them to consent before setting this parameter to TRUE."
+        },
+        locale: %Schema{
+          type: :string,
+          description: "The language of the confirmation email that will be sent"
+        },
+        # Pleroma-specific properties:
+        fullname: %Schema{type: :string, description: "Full name"},
+        bio: %Schema{type: :string, description: "Bio", default: ""},
+        captcha_solution: %Schema{
+          type: :string,
+          description: "Provider-specific captcha solution"
+        },
+        captcha_token: %Schema{type: :string, description: "Provider-specific captcha token"},
+        captcha_answer_data: %Schema{type: :string, description: "Provider-specific captcha data"},
+        token: %Schema{
+          type: :string,
+          description: "Invite token required when the registrations aren't public"
+        }
+      },
+      required: [:username, :password, :agreement],
+      example: %{
+        "username" => "cofe",
+        "email" => "cofe@example.com",
+        "password" => "secret",
+        "agreement" => "true",
+        "bio" => "☕️"
+      }
+    }
+  end
+
+  defp create_response do
+    %Schema{
+      title: "AccountCreateResponse",
+      description: "Response schema for an account",
+      type: :object,
+      properties: %{
+        token_type: %Schema{type: :string},
+        access_token: %Schema{type: :string},
+        scope: %Schema{type: :array, items: %Schema{type: :string}},
+        created_at: %Schema{type: :integer, format: :"date-time"}
+      },
+      example: %{
+        "access_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzk",
+        "created_at" => 1_585_918_714,
+        "scope" => ["read", "write", "follow", "push"],
+        "token_type" => "Bearer"
+      }
+    }
+  end
+
+  defp update_creadentials_request do
+    %Schema{
+      title: "AccountUpdateCredentialsRequest",
+      description: "POST body for creating an account",
+      type: :object,
+      properties: %{
+        bot: %Schema{
+          type: :boolean,
+          description: "Whether the account has a bot flag."
+        },
+        display_name: %Schema{
+          type: :string,
+          description: "The display name to use for the profile."
+        },
+        note: %Schema{type: :string, description: "The account bio."},
+        avatar: %Schema{
+          type: :string,
+          description: "Avatar image encoded using multipart/form-data",
+          format: :binary
+        },
+        header: %Schema{
+          type: :string,
+          description: "Header image encoded using multipart/form-data",
+          format: :binary
+        },
+        locked: %Schema{
+          type: :boolean,
+          description: "Whether manual approval of follow requests is required."
+        },
+        fields_attributes: %Schema{
+          oneOf: [
+            %Schema{type: :array, items: attribute_field()},
+            %Schema{type: :object, additionalProperties: %Schema{type: attribute_field()}}
+          ]
+        },
+        # NOTE: `source` field is not supported
+        #
+        # source: %Schema{
+        #   type: :object,
+        #   properties: %{
+        #     privacy: %Schema{type: :string},
+        #     sensitive: %Schema{type: :boolean},
+        #     language: %Schema{type: :string}
+        #   }
+        # },
+
+        # Pleroma-specific fields
+        no_rich_text: %Schema{
+          type: :boolean,
+          description: "html tags are stripped from all statuses requested from the API"
+        },
+        hide_followers: %Schema{type: :boolean, description: "user's followers will be hidden"},
+        hide_follows: %Schema{type: :boolean, description: "user's follows will be hidden"},
+        hide_followers_count: %Schema{
+          type: :boolean,
+          description: "user's follower count will be hidden"
+        },
+        hide_follows_count: %Schema{
+          type: :boolean,
+          description: "user's follow count will be hidden"
+        },
+        hide_favorites: %Schema{
+          type: :boolean,
+          description: "user's favorites timeline will be hidden"
+        },
+        show_role: %Schema{
+          type: :boolean,
+          description: "user's role (e.g admin, moderator) will be exposed to anyone in the
+        API"
+        },
+        default_scope: VisibilityScope,
+        pleroma_settings_store: %Schema{
+          type: :object,
+          description: "Opaque user settings to be saved on the backend."
+        },
+        skip_thread_containment: %Schema{
+          type: :boolean,
+          description: "Skip filtering out broken threads"
+        },
+        allow_following_move: %Schema{
+          type: :boolean,
+          description: "Allows automatically follow moved following accounts"
+        },
+        pleroma_background_image: %Schema{
+          type: :string,
+          description: "Sets the background image of the user.",
+          format: :binary
+        },
+        discoverable: %Schema{
+          type: :boolean,
+          description:
+            "Discovery of this account in search results and other services is allowed."
+        },
+        actor_type: ActorType
+      },
+      example: %{
+        bot: false,
+        display_name: "cofe",
+        note: "foobar",
+        fields_attributes: [%{name: "foo", value: "bar"}],
+        no_rich_text: false,
+        hide_followers: true,
+        hide_follows: false,
+        hide_followers_count: false,
+        hide_follows_count: false,
+        hide_favorites: false,
+        show_role: false,
+        default_scope: "private",
+        pleroma_settings_store: %{"pleroma-fe" => %{"key" => "val"}},
+        skip_thread_containment: false,
+        allow_following_move: false,
+        discoverable: false,
+        actor_type: "Person"
+      }
+    }
+  end
+
+  defp array_of_accounts do
+    %Schema{
+      title: "ArrayOfAccounts",
+      type: :array,
+      items: Account
+    }
+  end
+
+  defp array_of_relationships do
+    %Schema{
+      title: "ArrayOfRelationships",
+      description: "Response schema for account relationships",
+      type: :array,
+      items: AccountRelationship,
+      example: [
+        %{
+          "id" => "1",
+          "following" => true,
+          "showing_reblogs" => true,
+          "followed_by" => true,
+          "blocking" => false,
+          "blocked_by" => true,
+          "muting" => false,
+          "muting_notifications" => false,
+          "requested" => false,
+          "domain_blocking" => false,
+          "subscribing" => false,
+          "endorsed" => true
+        },
+        %{
+          "id" => "2",
+          "following" => true,
+          "showing_reblogs" => true,
+          "followed_by" => true,
+          "blocking" => false,
+          "blocked_by" => true,
+          "muting" => true,
+          "muting_notifications" => false,
+          "requested" => true,
+          "domain_blocking" => false,
+          "subscribing" => false,
+          "endorsed" => false
+        },
+        %{
+          "id" => "3",
+          "following" => true,
+          "showing_reblogs" => true,
+          "followed_by" => true,
+          "blocking" => true,
+          "blocked_by" => false,
+          "muting" => true,
+          "muting_notifications" => false,
+          "requested" => false,
+          "domain_blocking" => true,
+          "subscribing" => true,
+          "endorsed" => false
+        }
+      ]
+    }
+  end
+
+  defp follows_request do
+    %Schema{
+      title: "AccountFollowsRequest",
+      description: "POST body for muting an account",
+      type: :object,
+      properties: %{
+        uri: %Schema{type: :string, format: :uri}
+      },
+      required: [:uri]
+    }
+  end
+
+  defp mute_request do
+    %Schema{
+      title: "AccountMuteRequest",
+      description: "POST body for muting an account",
+      type: :object,
+      properties: %{
+        notifications: %Schema{
+          type: :boolean,
+          description: "Mute notifications in addition to statuses? Defaults to true.",
+          default: true
+        }
+      },
+      example: %{
+        "notifications" => true
+      }
+    }
+  end
+
+  defp list do
+    %Schema{
+      title: "List",
+      description: "Response schema for a list",
+      type: :object,
+      properties: %{
+        id: %Schema{type: :string},
+        title: %Schema{type: :string}
+      },
+      example: %{
+        "id" => "123",
+        "title" => "my list"
+      }
+    }
+  end
+
+  defp array_of_lists do
+    %Schema{
+      title: "ArrayOfLists",
+      description: "Response schema for lists",
+      type: :array,
+      items: list(),
+      example: [
+        %{"id" => "123", "title" => "my list"},
+        %{"id" => "1337", "title" => "anotehr list"}
+      ]
+    }
+  end
+
+  defp array_of_statuses do
+    %Schema{
+      title: "ArrayOfStatuses",
+      type: :array,
+      items: Status
+    }
+  end
+
+  defp attribute_field do
+    %Schema{
+      title: "AccountAttributeField",
+      description: "Request schema for account custom fields",
+      type: :object,
+      properties: %{
+        name: %Schema{type: :string},
+        value: %Schema{type: :string}
+      },
+      required: [:name, :value],
+      example: %{
+        "name" => "Website",
+        "value" => "https://pleroma.com"
+      }
+    }
+  end
+end
index 26d8dbd421a7331367ad73fca00587971ee44923..f6ccd073fec07d8348d33e20f257e4a1eabcee2a 100644 (file)
@@ -6,8 +6,6 @@ defmodule Pleroma.Web.ApiSpec.AppOperation do
   alias OpenApiSpex.Operation
   alias OpenApiSpex.Schema
   alias Pleroma.Web.ApiSpec.Helpers
-  alias Pleroma.Web.ApiSpec.Schemas.AppCreateRequest
-  alias Pleroma.Web.ApiSpec.Schemas.AppCreateResponse
 
   @spec open_api_operation(atom) :: Operation.t()
   def open_api_operation(action) do
@@ -22,9 +20,9 @@ defmodule Pleroma.Web.ApiSpec.AppOperation do
       summary: "Create an application",
       description: "Create a new application to obtain OAuth2 credentials",
       operationId: "AppController.create",
-      requestBody: Helpers.request_body("Parameters", AppCreateRequest, required: true),
+      requestBody: Helpers.request_body("Parameters", create_request(), required: true),
       responses: %{
-        200 => Operation.response("App", "application/json", AppCreateResponse),
+        200 => Operation.response("App", "application/json", create_response()),
         422 =>
           Operation.response(
             "Unprocessable Entity",
@@ -51,11 +49,7 @@ defmodule Pleroma.Web.ApiSpec.AppOperation do
       summary: "Verify your app works",
       description: "Confirm that the app's OAuth2 credentials work.",
       operationId: "AppController.verify_credentials",
-      security: [
-        %{
-          "oAuth" => ["read"]
-        }
-      ],
+      security: [%{"oAuth" => ["read"]}],
       responses: %{
         200 =>
           Operation.response("App", "application/json", %Schema{
@@ -93,4 +87,58 @@ defmodule Pleroma.Web.ApiSpec.AppOperation do
       }
     }
   end
+
+  defp create_request do
+    %Schema{
+      title: "AppCreateRequest",
+      description: "POST body for creating an app",
+      type: :object,
+      properties: %{
+        client_name: %Schema{type: :string, description: "A name for your application."},
+        redirect_uris: %Schema{
+          type: :string,
+          description:
+            "Where the user should be redirected after authorization. To display the authorization code to the user instead of redirecting to a web page, use `urn:ietf:wg:oauth:2.0:oob` in this parameter."
+        },
+        scopes: %Schema{
+          type: :string,
+          description: "Space separated list of scopes",
+          default: "read"
+        },
+        website: %Schema{type: :string, description: "A URL to the homepage of your app"}
+      },
+      required: [:client_name, :redirect_uris],
+      example: %{
+        "client_name" => "My App",
+        "redirect_uris" => "https://myapp.com/auth/callback",
+        "website" => "https://myapp.com/"
+      }
+    }
+  end
+
+  defp create_response do
+    %Schema{
+      title: "AppCreateResponse",
+      description: "Response schema for an app",
+      type: :object,
+      properties: %{
+        id: %Schema{type: :string},
+        name: %Schema{type: :string},
+        client_id: %Schema{type: :string},
+        client_secret: %Schema{type: :string},
+        redirect_uri: %Schema{type: :string},
+        vapid_key: %Schema{type: :string},
+        website: %Schema{type: :string, nullable: true}
+      },
+      example: %{
+        "id" => "123",
+        "name" => "My App",
+        "client_id" => "TWhM-tNSuncnqN7DBJmoyeLnk6K3iJJ71KKXxgL1hPM",
+        "client_secret" => "ZEaFUFmF0umgBX1qKJDjaU99Q31lDkOU8NutzTOoliw",
+        "vapid_key" =>
+          "BCk-QqERU0q-CfYZjcuB6lnyyOYfJ2AifKqfeGIm7Z-HiTU5T9eTG5GxVA0_OH5mMlI4UkkDTpaZwozy0TzdZ2M=",
+        "website" => "https://myapp.com/"
+      }
+    }
+  end
 end
index cf2215823f4c00bdd9a731999edf43cf2c224c3f..2f812ac77a710d0eb7e42414c7a431a2ffba5724 100644 (file)
@@ -4,7 +4,8 @@
 
 defmodule Pleroma.Web.ApiSpec.CustomEmojiOperation do
   alias OpenApiSpex.Operation
-  alias Pleroma.Web.ApiSpec.Schemas.CustomEmojisResponse
+  alias OpenApiSpex.Schema
+  alias Pleroma.Web.ApiSpec.Schemas.Emoji
 
   def open_api_operation(action) do
     operation = String.to_existing_atom("#{action}_operation")
@@ -18,7 +19,69 @@ defmodule Pleroma.Web.ApiSpec.CustomEmojiOperation do
       description: "Returns custom emojis that are available on the server.",
       operationId: "CustomEmojiController.index",
       responses: %{
-        200 => Operation.response("Custom Emojis", "application/json", CustomEmojisResponse)
+        200 => Operation.response("Custom Emojis", "application/json", resposnse())
+      }
+    }
+  end
+
+  defp resposnse do
+    %Schema{
+      title: "CustomEmojisResponse",
+      description: "Response schema for custom emojis",
+      type: :array,
+      items: custom_emoji(),
+      example: [
+        %{
+          "category" => "Fun",
+          "shortcode" => "blank",
+          "static_url" => "https://lain.com/emoji/blank.png",
+          "tags" => ["Fun"],
+          "url" => "https://lain.com/emoji/blank.png",
+          "visible_in_picker" => false
+        },
+        %{
+          "category" => "Gif,Fun",
+          "shortcode" => "firefox",
+          "static_url" => "https://lain.com/emoji/Firefox.gif",
+          "tags" => ["Gif", "Fun"],
+          "url" => "https://lain.com/emoji/Firefox.gif",
+          "visible_in_picker" => true
+        },
+        %{
+          "category" => "pack:mixed",
+          "shortcode" => "sadcat",
+          "static_url" => "https://lain.com/emoji/mixed/sadcat.png",
+          "tags" => ["pack:mixed"],
+          "url" => "https://lain.com/emoji/mixed/sadcat.png",
+          "visible_in_picker" => true
+        }
+      ]
+    }
+  end
+
+  defp custom_emoji do
+    %Schema{
+      title: "CustomEmoji",
+      description: "Schema for a CustomEmoji",
+      allOf: [
+        Emoji,
+        %Schema{
+          type: :object,
+          properties: %{
+            category: %Schema{type: :string},
+            tags: %Schema{type: :array}
+          }
+        }
+      ],
+      example: %{
+        "category" => "Fun",
+        "shortcode" => "aaaa",
+        "url" =>
+          "https://files.mastodon.social/custom_emojis/images/000/007/118/original/aaaa.png",
+        "static_url" =>
+          "https://files.mastodon.social/custom_emojis/images/000/007/118/static/aaaa.png",
+        "visible_in_picker" => true,
+        "tags" => ["Gif", "Fun"]
       }
     }
   end
index dd14837c3876d8f8d32cd4e431b251b5a65d91c3..3b7f51cebb152f71636f49d878de2d750d8c7b24 100644 (file)
@@ -6,8 +6,6 @@ defmodule Pleroma.Web.ApiSpec.DomainBlockOperation do
   alias OpenApiSpex.Operation
   alias OpenApiSpex.Schema
   alias Pleroma.Web.ApiSpec.Helpers
-  alias Pleroma.Web.ApiSpec.Schemas.DomainBlockRequest
-  alias Pleroma.Web.ApiSpec.Schemas.DomainBlocksResponse
 
   def open_api_operation(action) do
     operation = String.to_existing_atom("#{action}_operation")
@@ -22,7 +20,13 @@ defmodule Pleroma.Web.ApiSpec.DomainBlockOperation do
       security: [%{"oAuth" => ["follow", "read:blocks"]}],
       operationId: "DomainBlockController.index",
       responses: %{
-        200 => Operation.response("Domain blocks", "application/json", DomainBlocksResponse)
+        200 =>
+          Operation.response("Domain blocks", "application/json", %Schema{
+            description: "Response schema for domain blocks",
+            type: :array,
+            items: %Schema{type: :string},
+            example: ["google.com", "facebook.com"]
+          })
       }
     }
   end
@@ -40,7 +44,7 @@ defmodule Pleroma.Web.ApiSpec.DomainBlockOperation do
       - prevent following new users from it (but does not remove existing follows)
       """,
       operationId: "DomainBlockController.create",
-      requestBody: Helpers.request_body("Parameters", DomainBlockRequest, required: true),
+      requestBody: domain_block_request(),
       security: [%{"oAuth" => ["follow", "write:blocks"]}],
       responses: %{
         200 => Operation.response("Empty object", "application/json", %Schema{type: :object})
@@ -54,11 +58,28 @@ defmodule Pleroma.Web.ApiSpec.DomainBlockOperation do
       summary: "Unblock a domain",
       description: "Remove a domain block, if it exists in the user's array of blocked domains.",
       operationId: "DomainBlockController.delete",
-      requestBody: Helpers.request_body("Parameters", DomainBlockRequest, required: true),
+      requestBody: domain_block_request(),
       security: [%{"oAuth" => ["follow", "write:blocks"]}],
       responses: %{
         200 => Operation.response("Empty object", "application/json", %Schema{type: :object})
       }
     }
   end
+
+  defp domain_block_request do
+    Helpers.request_body(
+      "Parameters",
+      %Schema{
+        type: :object,
+        properties: %{
+          domain: %Schema{type: :string}
+        },
+        required: [:domain]
+      },
+      required: true,
+      example: %{
+        "domain" => "facebook.com"
+      }
+    )
+  end
 end
diff --git a/lib/pleroma/web/api_spec/render_error.ex b/lib/pleroma/web/api_spec/render_error.ex
new file mode 100644 (file)
index 0000000..b5877ca
--- /dev/null
@@ -0,0 +1,231 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.RenderError do
+  @behaviour Plug
+
+  import Plug.Conn, only: [put_status: 2]
+  import Phoenix.Controller, only: [json: 2]
+  import Pleroma.Web.Gettext
+
+  @impl Plug
+  def init(opts), do: opts
+
+  @impl Plug
+
+  def call(conn, errors) do
+    errors =
+      Enum.map(errors, fn
+        %{name: nil} = err ->
+          %OpenApiSpex.Cast.Error{err | name: List.last(err.path)}
+
+        err ->
+          err
+      end)
+
+    conn
+    |> put_status(:bad_request)
+    |> json(%{
+      error: errors |> Enum.map(&message/1) |> Enum.join(" "),
+      errors: errors |> Enum.map(&render_error/1)
+    })
+  end
+
+  defp render_error(error) do
+    pointer = OpenApiSpex.path_to_string(error)
+
+    %{
+      title: "Invalid value",
+      source: %{
+        pointer: pointer
+      },
+      message: OpenApiSpex.Cast.Error.message(error)
+    }
+  end
+
+  defp message(%{reason: :invalid_schema_type, type: type, name: name}) do
+    gettext("%{name} - Invalid schema.type. Got: %{type}.",
+      name: name,
+      type: inspect(type)
+    )
+  end
+
+  defp message(%{reason: :null_value, name: name} = error) do
+    case error.type do
+      nil ->
+        gettext("%{name} - null value.", name: name)
+
+      type ->
+        gettext("%{name} - null value where %{type} expected.",
+          name: name,
+          type: type
+        )
+    end
+  end
+
+  defp message(%{reason: :all_of, meta: %{invalid_schema: invalid_schema}}) do
+    gettext(
+      "Failed to cast value as %{invalid_schema}. Value must be castable using `allOf` schemas listed.",
+      invalid_schema: invalid_schema
+    )
+  end
+
+  defp message(%{reason: :any_of, meta: %{failed_schemas: failed_schemas}}) do
+    gettext("Failed to cast value using any of: %{failed_schemas}.",
+      failed_schemas: failed_schemas
+    )
+  end
+
+  defp message(%{reason: :one_of, meta: %{failed_schemas: failed_schemas}}) do
+    gettext("Failed to cast value to one of: %{failed_schemas}.", failed_schemas: failed_schemas)
+  end
+
+  defp message(%{reason: :min_length, length: length, name: name}) do
+    gettext("%{name} - String length is smaller than minLength: %{length}.",
+      name: name,
+      length: length
+    )
+  end
+
+  defp message(%{reason: :max_length, length: length, name: name}) do
+    gettext("%{name} - String length is larger than maxLength: %{length}.",
+      name: name,
+      length: length
+    )
+  end
+
+  defp message(%{reason: :unique_items, name: name}) do
+    gettext("%{name} - Array items must be unique.", name: name)
+  end
+
+  defp message(%{reason: :min_items, length: min, value: array, name: name}) do
+    gettext("%{name} - Array length %{length} is smaller than minItems: %{min}.",
+      name: name,
+      length: length(array),
+      min: min
+    )
+  end
+
+  defp message(%{reason: :max_items, length: max, value: array, name: name}) do
+    gettext("%{name} - Array length %{length} is larger than maxItems: %{}.",
+      name: name,
+      length: length(array),
+      max: max
+    )
+  end
+
+  defp message(%{reason: :multiple_of, length: multiple, value: count, name: name}) do
+    gettext("%{name} - %{count} is not a multiple of %{multiple}.",
+      name: name,
+      count: count,
+      multiple: multiple
+    )
+  end
+
+  defp message(%{reason: :exclusive_max, length: max, value: value, name: name})
+       when value >= max do
+    gettext("%{name} - %{value} is larger than exclusive maximum %{max}.",
+      name: name,
+      value: value,
+      max: max
+    )
+  end
+
+  defp message(%{reason: :maximum, length: max, value: value, name: name})
+       when value > max do
+    gettext("%{name} - %{value} is larger than inclusive maximum %{max}.",
+      name: name,
+      value: value,
+      max: max
+    )
+  end
+
+  defp message(%{reason: :exclusive_multiple, length: min, value: value, name: name})
+       when value <= min do
+    gettext("%{name} - %{value} is smaller than exclusive minimum %{min}.",
+      name: name,
+      value: value,
+      min: min
+    )
+  end
+
+  defp message(%{reason: :minimum, length: min, value: value, name: name})
+       when value < min do
+    gettext("%{name} - %{value} is smaller than inclusive minimum %{min}.",
+      name: name,
+      value: value,
+      min: min
+    )
+  end
+
+  defp message(%{reason: :invalid_type, type: type, value: value, name: name}) do
+    gettext("%{name} - Invalid %{type}. Got: %{value}.",
+      name: name,
+      value: OpenApiSpex.TermType.type(value),
+      type: type
+    )
+  end
+
+  defp message(%{reason: :invalid_format, format: format, name: name}) do
+    gettext("%{name} - Invalid format. Expected %{format}.", name: name, format: inspect(format))
+  end
+
+  defp message(%{reason: :invalid_enum, name: name}) do
+    gettext("%{name} - Invalid value for enum.", name: name)
+  end
+
+  defp message(%{reason: :polymorphic_failed, type: polymorphic_type}) do
+    gettext("Failed to cast to any schema in %{polymorphic_type}",
+      polymorphic_type: polymorphic_type
+    )
+  end
+
+  defp message(%{reason: :unexpected_field, name: name}) do
+    gettext("Unexpected field: %{name}.", name: safe_string(name))
+  end
+
+  defp message(%{reason: :no_value_for_discriminator, name: field}) do
+    gettext("Value used as discriminator for `%{field}` matches no schemas.", name: field)
+  end
+
+  defp message(%{reason: :invalid_discriminator_value, name: field}) do
+    gettext("No value provided for required discriminator `%{field}`.", name: field)
+  end
+
+  defp message(%{reason: :unknown_schema, name: name}) do
+    gettext("Unknown schema: %{name}.", name: name)
+  end
+
+  defp message(%{reason: :missing_field, name: name}) do
+    gettext("Missing field: %{name}.", name: name)
+  end
+
+  defp message(%{reason: :missing_header, name: name}) do
+    gettext("Missing header: %{name}.", name: name)
+  end
+
+  defp message(%{reason: :invalid_header, name: name}) do
+    gettext("Invalid value for header: %{name}.", name: name)
+  end
+
+  defp message(%{reason: :max_properties, meta: meta}) do
+    gettext(
+      "Object property count %{property_count} is greater than maxProperties: %{max_properties}.",
+      property_count: meta.property_count,
+      max_properties: meta.max_properties
+    )
+  end
+
+  defp message(%{reason: :min_properties, meta: meta}) do
+    gettext(
+      "Object property count %{property_count} is less than minProperties: %{min_properties}",
+      property_count: meta.property_count,
+      min_properties: meta.min_properties
+    )
+  end
+
+  defp safe_string(string) do
+    to_string(string) |> String.slice(0..39)
+  end
+end
diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex
new file mode 100644 (file)
index 0000000..d54e215
--- /dev/null
@@ -0,0 +1,167 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Schemas.Account do
+  alias OpenApiSpex.Schema
+  alias Pleroma.Web.ApiSpec.Schemas.AccountField
+  alias Pleroma.Web.ApiSpec.Schemas.AccountRelationship
+  alias Pleroma.Web.ApiSpec.Schemas.ActorType
+  alias Pleroma.Web.ApiSpec.Schemas.Emoji
+  alias Pleroma.Web.ApiSpec.Schemas.FlakeID
+  alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
+
+  require OpenApiSpex
+
+  OpenApiSpex.schema(%{
+    title: "Account",
+    description: "Response schema for an account",
+    type: :object,
+    properties: %{
+      acct: %Schema{type: :string},
+      avatar_static: %Schema{type: :string, format: :uri},
+      avatar: %Schema{type: :string, format: :uri},
+      bot: %Schema{type: :boolean},
+      created_at: %Schema{type: :string, format: "date-time"},
+      display_name: %Schema{type: :string},
+      emojis: %Schema{type: :array, items: Emoji},
+      fields: %Schema{type: :array, items: AccountField},
+      follow_requests_count: %Schema{type: :integer},
+      followers_count: %Schema{type: :integer},
+      following_count: %Schema{type: :integer},
+      header_static: %Schema{type: :string, format: :uri},
+      header: %Schema{type: :string, format: :uri},
+      id: FlakeID,
+      locked: %Schema{type: :boolean},
+      note: %Schema{type: :string, format: :html},
+      statuses_count: %Schema{type: :integer},
+      url: %Schema{type: :string, format: :uri},
+      username: %Schema{type: :string},
+      pleroma: %Schema{
+        type: :object,
+        properties: %{
+          allow_following_move: %Schema{type: :boolean},
+          background_image: %Schema{type: :string, nullable: true},
+          chat_token: %Schema{type: :string},
+          confirmation_pending: %Schema{type: :boolean},
+          hide_favorites: %Schema{type: :boolean},
+          hide_followers_count: %Schema{type: :boolean},
+          hide_followers: %Schema{type: :boolean},
+          hide_follows_count: %Schema{type: :boolean},
+          hide_follows: %Schema{type: :boolean},
+          is_admin: %Schema{type: :boolean},
+          is_moderator: %Schema{type: :boolean},
+          skip_thread_containment: %Schema{type: :boolean},
+          tags: %Schema{type: :array, items: %Schema{type: :string}},
+          unread_conversation_count: %Schema{type: :integer},
+          notification_settings: %Schema{
+            type: :object,
+            properties: %{
+              followers: %Schema{type: :boolean},
+              follows: %Schema{type: :boolean},
+              non_followers: %Schema{type: :boolean},
+              non_follows: %Schema{type: :boolean},
+              privacy_option: %Schema{type: :boolean}
+            }
+          },
+          relationship: AccountRelationship,
+          settings_store: %Schema{
+            type: :object
+          }
+        }
+      },
+      source: %Schema{
+        type: :object,
+        properties: %{
+          fields: %Schema{type: :array, items: AccountField},
+          note: %Schema{type: :string},
+          privacy: VisibilityScope,
+          sensitive: %Schema{type: :boolean},
+          pleroma: %Schema{
+            type: :object,
+            properties: %{
+              actor_type: ActorType,
+              discoverable: %Schema{type: :boolean},
+              no_rich_text: %Schema{type: :boolean},
+              show_role: %Schema{type: :boolean}
+            }
+          }
+        }
+      }
+    },
+    example: %{
+      "acct" => "foobar",
+      "avatar" => "https://mypleroma.com/images/avi.png",
+      "avatar_static" => "https://mypleroma.com/images/avi.png",
+      "bot" => false,
+      "created_at" => "2020-03-24T13:05:58.000Z",
+      "display_name" => "foobar",
+      "emojis" => [],
+      "fields" => [],
+      "follow_requests_count" => 0,
+      "followers_count" => 0,
+      "following_count" => 1,
+      "header" => "https://mypleroma.com/images/banner.png",
+      "header_static" => "https://mypleroma.com/images/banner.png",
+      "id" => "9tKi3esbG7OQgZ2920",
+      "locked" => false,
+      "note" => "cofe",
+      "pleroma" => %{
+        "allow_following_move" => true,
+        "background_image" => nil,
+        "confirmation_pending" => true,
+        "hide_favorites" => true,
+        "hide_followers" => false,
+        "hide_followers_count" => false,
+        "hide_follows" => false,
+        "hide_follows_count" => false,
+        "is_admin" => false,
+        "is_moderator" => false,
+        "skip_thread_containment" => false,
+        "chat_token" =>
+          "SFMyNTY.g3QAAAACZAAEZGF0YW0AAAASOXRLaTNlc2JHN09RZ1oyOTIwZAAGc2lnbmVkbgYARNplS3EB.Mb_Iaqew2bN1I1o79B_iP7encmVCpTKC4OtHZRxdjKc",
+        "unread_conversation_count" => 0,
+        "tags" => [],
+        "notification_settings" => %{
+          "followers" => true,
+          "follows" => true,
+          "non_followers" => true,
+          "non_follows" => true,
+          "privacy_option" => false
+        },
+        "relationship" => %{
+          "blocked_by" => false,
+          "blocking" => false,
+          "domain_blocking" => false,
+          "endorsed" => false,
+          "followed_by" => false,
+          "following" => false,
+          "id" => "9tKi3esbG7OQgZ2920",
+          "muting" => false,
+          "muting_notifications" => false,
+          "requested" => false,
+          "showing_reblogs" => true,
+          "subscribing" => false
+        },
+        "settings_store" => %{
+          "pleroma-fe" => %{}
+        }
+      },
+      "source" => %{
+        "fields" => [],
+        "note" => "foobar",
+        "pleroma" => %{
+          "actor_type" => "Person",
+          "discoverable" => false,
+          "no_rich_text" => false,
+          "show_role" => true
+        },
+        "privacy" => "public",
+        "sensitive" => false
+      },
+      "statuses_count" => 0,
+      "url" => "https://mypleroma.com/users/foobar",
+      "username" => "foobar"
+    }
+  })
+end
diff --git a/lib/pleroma/web/api_spec/schemas/account_field.ex b/lib/pleroma/web/api_spec/schemas/account_field.ex
new file mode 100644 (file)
index 0000000..fa97073
--- /dev/null
@@ -0,0 +1,26 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Schemas.AccountField do
+  alias OpenApiSpex.Schema
+
+  require OpenApiSpex
+
+  OpenApiSpex.schema(%{
+    title: "AccountField",
+    description: "Response schema for account custom fields",
+    type: :object,
+    properties: %{
+      name: %Schema{type: :string},
+      value: %Schema{type: :string, format: :html},
+      verified_at: %Schema{type: :string, format: :"date-time", nullable: true}
+    },
+    example: %{
+      "name" => "Website",
+      "value" =>
+        "<a href=\"https://pleroma.com\" rel=\"me nofollow noopener noreferrer\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"\">pleroma.com</span><span class=\"invisible\"></span></a>",
+      "verified_at" => "2019-08-29T04:14:55.571+00:00"
+    }
+  })
+end
diff --git a/lib/pleroma/web/api_spec/schemas/account_relationship.ex b/lib/pleroma/web/api_spec/schemas/account_relationship.ex
new file mode 100644 (file)
index 0000000..8b98266
--- /dev/null
@@ -0,0 +1,44 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationship do
+  alias OpenApiSpex.Schema
+  alias Pleroma.Web.ApiSpec.Schemas.FlakeID
+
+  require OpenApiSpex
+
+  OpenApiSpex.schema(%{
+    title: "AccountRelationship",
+    description: "Response schema for relationship",
+    type: :object,
+    properties: %{
+      blocked_by: %Schema{type: :boolean},
+      blocking: %Schema{type: :boolean},
+      domain_blocking: %Schema{type: :boolean},
+      endorsed: %Schema{type: :boolean},
+      followed_by: %Schema{type: :boolean},
+      following: %Schema{type: :boolean},
+      id: FlakeID,
+      muting: %Schema{type: :boolean},
+      muting_notifications: %Schema{type: :boolean},
+      requested: %Schema{type: :boolean},
+      showing_reblogs: %Schema{type: :boolean},
+      subscribing: %Schema{type: :boolean}
+    },
+    example: %{
+      "blocked_by" => false,
+      "blocking" => false,
+      "domain_blocking" => false,
+      "endorsed" => false,
+      "followed_by" => false,
+      "following" => false,
+      "id" => "9tKi3esbG7OQgZ2920",
+      "muting" => false,
+      "muting_notifications" => false,
+      "requested" => false,
+      "showing_reblogs" => true,
+      "subscribing" => false
+    }
+  })
+end
diff --git a/lib/pleroma/web/api_spec/schemas/actor_type.ex b/lib/pleroma/web/api_spec/schemas/actor_type.ex
new file mode 100644 (file)
index 0000000..ac9b466
--- /dev/null
@@ -0,0 +1,13 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Schemas.ActorType do
+  require OpenApiSpex
+
+  OpenApiSpex.schema(%{
+    title: "ActorType",
+    type: :string,
+    enum: ["Application", "Group", "Organization", "Person", "Service"]
+  })
+end
similarity index 56%
rename from lib/pleroma/web/api_spec/schemas/domain_block_request.ex
rename to lib/pleroma/web/api_spec/schemas/api_error.ex
index ee9238361aea1a6acbaf7b5f10a8cb51c17bd380..5815df94c657c27c2e7e4a040f9faba53dc332ff 100644 (file)
@@ -2,19 +2,18 @@
 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
 # SPDX-License-Identifier: AGPL-3.0-only
 
-defmodule Pleroma.Web.ApiSpec.Schemas.DomainBlockRequest do
+defmodule Pleroma.Web.ApiSpec.Schemas.ApiError do
   alias OpenApiSpex.Schema
+
   require OpenApiSpex
 
   OpenApiSpex.schema(%{
-    title: "DomainBlockRequest",
+    title: "ApiError",
+    description: "Response schema for API error",
     type: :object,
-    properties: %{
-      domain: %Schema{type: :string}
-    },
-    required: [:domain],
+    properties: %{error: %Schema{type: :string}},
     example: %{
-      "domain" => "facebook.com"
+      "error" => "Something went wrong"
     }
   })
 end
diff --git a/lib/pleroma/web/api_spec/schemas/app_create_request.ex b/lib/pleroma/web/api_spec/schemas/app_create_request.ex
deleted file mode 100644 (file)
index 8a83abe..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.ApiSpec.Schemas.AppCreateRequest do
-  alias OpenApiSpex.Schema
-  require OpenApiSpex
-
-  OpenApiSpex.schema(%{
-    title: "AppCreateRequest",
-    description: "POST body for creating an app",
-    type: :object,
-    properties: %{
-      client_name: %Schema{type: :string, description: "A name for your application."},
-      redirect_uris: %Schema{
-        type: :string,
-        description:
-          "Where the user should be redirected after authorization. To display the authorization code to the user instead of redirecting to a web page, use `urn:ietf:wg:oauth:2.0:oob` in this parameter."
-      },
-      scopes: %Schema{
-        type: :string,
-        description: "Space separated list of scopes. If none is provided, defaults to `read`."
-      },
-      website: %Schema{type: :string, description: "A URL to the homepage of your app"}
-    },
-    required: [:client_name, :redirect_uris],
-    example: %{
-      "client_name" => "My App",
-      "redirect_uris" => "https://myapp.com/auth/callback",
-      "website" => "https://myapp.com/"
-    }
-  })
-end
diff --git a/lib/pleroma/web/api_spec/schemas/app_create_response.ex b/lib/pleroma/web/api_spec/schemas/app_create_response.ex
deleted file mode 100644 (file)
index f290fb0..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.ApiSpec.Schemas.AppCreateResponse do
-  alias OpenApiSpex.Schema
-
-  require OpenApiSpex
-
-  OpenApiSpex.schema(%{
-    title: "AppCreateResponse",
-    description: "Response schema for an app",
-    type: :object,
-    properties: %{
-      id: %Schema{type: :string},
-      name: %Schema{type: :string},
-      client_id: %Schema{type: :string},
-      client_secret: %Schema{type: :string},
-      redirect_uri: %Schema{type: :string},
-      vapid_key: %Schema{type: :string},
-      website: %Schema{type: :string, nullable: true}
-    },
-    example: %{
-      "id" => "123",
-      "name" => "My App",
-      "client_id" => "TWhM-tNSuncnqN7DBJmoyeLnk6K3iJJ71KKXxgL1hPM",
-      "client_secret" => "ZEaFUFmF0umgBX1qKJDjaU99Q31lDkOU8NutzTOoliw",
-      "vapid_key" =>
-        "BCk-QqERU0q-CfYZjcuB6lnyyOYfJ2AifKqfeGIm7Z-HiTU5T9eTG5GxVA0_OH5mMlI4UkkDTpaZwozy0TzdZ2M=",
-      "website" => "https://myapp.com/"
-    }
-  })
-end
diff --git a/lib/pleroma/web/api_spec/schemas/boolean_like.ex b/lib/pleroma/web/api_spec/schemas/boolean_like.ex
new file mode 100644 (file)
index 0000000..f3bfb74
--- /dev/null
@@ -0,0 +1,36 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Schemas.BooleanLike do
+  alias OpenApiSpex.Schema
+
+  require OpenApiSpex
+
+  OpenApiSpex.schema(%{
+    title: "BooleanLike",
+    description: """
+    The following values will be treated as `false`:
+      - false
+      - 0
+      - "0",
+      - "f",
+      - "F",
+      - "false",
+      - "FALSE",
+      - "off",
+      - "OFF"
+
+    All other non-null values will be treated as `true`
+    """,
+    anyOf: [
+      %Schema{type: :boolean},
+      %Schema{type: :string},
+      %Schema{type: :integer}
+    ]
+  })
+
+  def after_cast(value, _schmea) do
+    {:ok, Pleroma.Web.ControllerHelper.truthy_param?(value)}
+  end
+end
diff --git a/lib/pleroma/web/api_spec/schemas/custom_emoji.ex b/lib/pleroma/web/api_spec/schemas/custom_emoji.ex
deleted file mode 100644 (file)
index 5531b20..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.ApiSpec.Schemas.CustomEmoji do
-  alias OpenApiSpex.Schema
-
-  require OpenApiSpex
-
-  OpenApiSpex.schema(%{
-    title: "CustomEmoji",
-    description: "Response schema for an CustomEmoji",
-    type: :object,
-    properties: %{
-      shortcode: %Schema{type: :string},
-      url: %Schema{type: :string},
-      static_url: %Schema{type: :string},
-      visible_in_picker: %Schema{type: :boolean},
-      category: %Schema{type: :string},
-      tags: %Schema{type: :array}
-    },
-    example: %{
-      "shortcode" => "aaaa",
-      "url" => "https://files.mastodon.social/custom_emojis/images/000/007/118/original/aaaa.png",
-      "static_url" =>
-        "https://files.mastodon.social/custom_emojis/images/000/007/118/static/aaaa.png",
-      "visible_in_picker" => true
-    }
-  })
-end
diff --git a/lib/pleroma/web/api_spec/schemas/custom_emojis_response.ex b/lib/pleroma/web/api_spec/schemas/custom_emojis_response.ex
deleted file mode 100644 (file)
index 01582a6..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.ApiSpec.Schemas.CustomEmojisResponse do
-  alias Pleroma.Web.ApiSpec.Schemas.CustomEmoji
-
-  require OpenApiSpex
-
-  OpenApiSpex.schema(%{
-    title: "CustomEmojisResponse",
-    description: "Response schema for custom emojis",
-    type: :array,
-    items: CustomEmoji,
-    example: [
-      %{
-        "category" => "Fun",
-        "shortcode" => "blank",
-        "static_url" => "https://lain.com/emoji/blank.png",
-        "tags" => ["Fun"],
-        "url" => "https://lain.com/emoji/blank.png",
-        "visible_in_picker" => true
-      },
-      %{
-        "category" => "Gif,Fun",
-        "shortcode" => "firefox",
-        "static_url" => "https://lain.com/emoji/Firefox.gif",
-        "tags" => ["Gif", "Fun"],
-        "url" => "https://lain.com/emoji/Firefox.gif",
-        "visible_in_picker" => true
-      },
-      %{
-        "category" => "pack:mixed",
-        "shortcode" => "sadcat",
-        "static_url" => "https://lain.com/emoji/mixed/sadcat.png",
-        "tags" => ["pack:mixed"],
-        "url" => "https://lain.com/emoji/mixed/sadcat.png",
-        "visible_in_picker" => true
-      }
-    ]
-  })
-end
diff --git a/lib/pleroma/web/api_spec/schemas/domain_blocks_response.ex b/lib/pleroma/web/api_spec/schemas/domain_blocks_response.ex
deleted file mode 100644 (file)
index d895aca..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.ApiSpec.Schemas.DomainBlocksResponse do
-  require OpenApiSpex
-  alias OpenApiSpex.Schema
-
-  OpenApiSpex.schema(%{
-    title: "DomainBlocksResponse",
-    description: "Response schema for domain blocks",
-    type: :array,
-    items: %Schema{type: :string},
-    example: ["google.com", "facebook.com"]
-  })
-end
diff --git a/lib/pleroma/web/api_spec/schemas/emoji.ex b/lib/pleroma/web/api_spec/schemas/emoji.ex
new file mode 100644 (file)
index 0000000..26f35e6
--- /dev/null
@@ -0,0 +1,29 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Schemas.Emoji do
+  alias OpenApiSpex.Schema
+
+  require OpenApiSpex
+
+  OpenApiSpex.schema(%{
+    title: "Emoji",
+    description: "Response schema for an emoji",
+    type: :object,
+    properties: %{
+      shortcode: %Schema{type: :string},
+      url: %Schema{type: :string, format: :uri},
+      static_url: %Schema{type: :string, format: :uri},
+      visible_in_picker: %Schema{type: :boolean}
+    },
+    example: %{
+      "shortcode" => "fatyoshi",
+      "url" =>
+        "https://files.mastodon.social/custom_emojis/images/000/023/920/original/e57ecb623faa0dc9.png",
+      "static_url" =>
+        "https://files.mastodon.social/custom_emojis/images/000/023/920/static/e57ecb623faa0dc9.png",
+      "visible_in_picker" => true
+    }
+  })
+end
diff --git a/lib/pleroma/web/api_spec/schemas/flake_id.ex b/lib/pleroma/web/api_spec/schemas/flake_id.ex
new file mode 100644 (file)
index 0000000..3b5f647
--- /dev/null
@@ -0,0 +1,14 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Schemas.FlakeID do
+  require OpenApiSpex
+
+  OpenApiSpex.schema(%{
+    title: "FlakeID",
+    description:
+      "Pleroma uses 128-bit ids as opposed to Mastodon's 64 bits. However just like Mastodon's ids they are lexically sortable strings",
+    type: :string
+  })
+end
diff --git a/lib/pleroma/web/api_spec/schemas/poll.ex b/lib/pleroma/web/api_spec/schemas/poll.ex
new file mode 100644 (file)
index 0000000..0474b55
--- /dev/null
@@ -0,0 +1,36 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Schemas.Poll do
+  alias OpenApiSpex.Schema
+  alias Pleroma.Web.ApiSpec.Schemas.Emoji
+  alias Pleroma.Web.ApiSpec.Schemas.FlakeID
+
+  require OpenApiSpex
+
+  OpenApiSpex.schema(%{
+    title: "Poll",
+    description: "Response schema for account custom fields",
+    type: :object,
+    properties: %{
+      id: FlakeID,
+      expires_at: %Schema{type: :string, format: "date-time"},
+      expired: %Schema{type: :boolean},
+      multiple: %Schema{type: :boolean},
+      votes_count: %Schema{type: :integer},
+      voted: %Schema{type: :boolean},
+      emojis: %Schema{type: :array, items: Emoji},
+      options: %Schema{
+        type: :array,
+        items: %Schema{
+          type: :object,
+          properties: %{
+            title: %Schema{type: :string},
+            votes_count: %Schema{type: :integer}
+          }
+        }
+      }
+    }
+  })
+end
diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex
new file mode 100644 (file)
index 0000000..aef0588
--- /dev/null
@@ -0,0 +1,226 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Schemas.Status do
+  alias OpenApiSpex.Schema
+  alias Pleroma.Web.ApiSpec.Schemas.Account
+  alias Pleroma.Web.ApiSpec.Schemas.Emoji
+  alias Pleroma.Web.ApiSpec.Schemas.FlakeID
+  alias Pleroma.Web.ApiSpec.Schemas.Poll
+  alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
+
+  require OpenApiSpex
+
+  OpenApiSpex.schema(%{
+    title: "Status",
+    description: "Response schema for a status",
+    type: :object,
+    properties: %{
+      account: Account,
+      application: %Schema{
+        type: :object,
+        properties: %{
+          name: %Schema{type: :string},
+          website: %Schema{type: :string, nullable: true, format: :uri}
+        }
+      },
+      bookmarked: %Schema{type: :boolean},
+      card: %Schema{
+        type: :object,
+        nullable: true,
+        properties: %{
+          type: %Schema{type: :string, enum: ["link", "photo", "video", "rich"]},
+          provider_name: %Schema{type: :string, nullable: true},
+          provider_url: %Schema{type: :string, format: :uri},
+          url: %Schema{type: :string, format: :uri},
+          image: %Schema{type: :string, nullable: true, format: :uri},
+          title: %Schema{type: :string},
+          description: %Schema{type: :string}
+        }
+      },
+      content: %Schema{type: :string, format: :html},
+      created_at: %Schema{type: :string, format: "date-time"},
+      emojis: %Schema{type: :array, items: Emoji},
+      favourited: %Schema{type: :boolean},
+      favourites_count: %Schema{type: :integer},
+      id: FlakeID,
+      in_reply_to_account_id: %Schema{type: :string, nullable: true},
+      in_reply_to_id: %Schema{type: :string, nullable: true},
+      language: %Schema{type: :string, nullable: true},
+      media_attachments: %Schema{
+        type: :array,
+        items: %Schema{
+          type: :object,
+          properties: %{
+            id: %Schema{type: :string},
+            url: %Schema{type: :string, format: :uri},
+            remote_url: %Schema{type: :string, format: :uri},
+            preview_url: %Schema{type: :string, format: :uri},
+            text_url: %Schema{type: :string, format: :uri},
+            description: %Schema{type: :string},
+            type: %Schema{type: :string, enum: ["image", "video", "audio", "unknown"]},
+            pleroma: %Schema{
+              type: :object,
+              properties: %{mime_type: %Schema{type: :string}}
+            }
+          }
+        }
+      },
+      mentions: %Schema{
+        type: :array,
+        items: %Schema{
+          type: :object,
+          properties: %{
+            id: %Schema{type: :string},
+            acct: %Schema{type: :string},
+            username: %Schema{type: :string},
+            url: %Schema{type: :string, format: :uri}
+          }
+        }
+      },
+      muted: %Schema{type: :boolean},
+      pinned: %Schema{type: :boolean},
+      pleroma: %Schema{
+        type: :object,
+        properties: %{
+          content: %Schema{type: :object, additionalProperties: %Schema{type: :string}},
+          conversation_id: %Schema{type: :integer},
+          direct_conversation_id: %Schema{type: :string, nullable: true},
+          emoji_reactions: %Schema{
+            type: :array,
+            items: %Schema{
+              type: :object,
+              properties: %{
+                name: %Schema{type: :string},
+                count: %Schema{type: :integer},
+                me: %Schema{type: :boolean}
+              }
+            }
+          },
+          expires_at: %Schema{type: :string, format: "date-time", nullable: true},
+          in_reply_to_account_acct: %Schema{type: :string, nullable: true},
+          local: %Schema{type: :boolean},
+          spoiler_text: %Schema{type: :object, additionalProperties: %Schema{type: :string}},
+          thread_muted: %Schema{type: :boolean}
+        }
+      },
+      poll: %Schema{type: Poll, nullable: true},
+      reblog: %Schema{
+        allOf: [%OpenApiSpex.Reference{"$ref": "#/components/schemas/Status"}],
+        nullable: true
+      },
+      reblogged: %Schema{type: :boolean},
+      reblogs_count: %Schema{type: :integer},
+      replies_count: %Schema{type: :integer},
+      sensitive: %Schema{type: :boolean},
+      spoiler_text: %Schema{type: :string},
+      tags: %Schema{
+        type: :array,
+        items: %Schema{
+          type: :object,
+          properties: %{
+            name: %Schema{type: :string},
+            url: %Schema{type: :string, format: :uri}
+          }
+        }
+      },
+      uri: %Schema{type: :string, format: :uri},
+      url: %Schema{type: :string, nullable: true, format: :uri},
+      visibility: VisibilityScope
+    },
+    example: %{
+      "account" => %{
+        "acct" => "nick6",
+        "avatar" => "http://localhost:4001/images/avi.png",
+        "avatar_static" => "http://localhost:4001/images/avi.png",
+        "bot" => false,
+        "created_at" => "2020-04-07T19:48:51.000Z",
+        "display_name" => "Test テスト User 6",
+        "emojis" => [],
+        "fields" => [],
+        "followers_count" => 1,
+        "following_count" => 0,
+        "header" => "http://localhost:4001/images/banner.png",
+        "header_static" => "http://localhost:4001/images/banner.png",
+        "id" => "9toJCsKN7SmSf3aj5c",
+        "locked" => false,
+        "note" => "Tester Number 6",
+        "pleroma" => %{
+          "background_image" => nil,
+          "confirmation_pending" => false,
+          "hide_favorites" => true,
+          "hide_followers" => false,
+          "hide_followers_count" => false,
+          "hide_follows" => false,
+          "hide_follows_count" => false,
+          "is_admin" => false,
+          "is_moderator" => false,
+          "relationship" => %{
+            "blocked_by" => false,
+            "blocking" => false,
+            "domain_blocking" => false,
+            "endorsed" => false,
+            "followed_by" => false,
+            "following" => true,
+            "id" => "9toJCsKN7SmSf3aj5c",
+            "muting" => false,
+            "muting_notifications" => false,
+            "requested" => false,
+            "showing_reblogs" => true,
+            "subscribing" => false
+          },
+          "skip_thread_containment" => false,
+          "tags" => []
+        },
+        "source" => %{
+          "fields" => [],
+          "note" => "Tester Number 6",
+          "pleroma" => %{"actor_type" => "Person", "discoverable" => false},
+          "sensitive" => false
+        },
+        "statuses_count" => 1,
+        "url" => "http://localhost:4001/users/nick6",
+        "username" => "nick6"
+      },
+      "application" => %{"name" => "Web", "website" => nil},
+      "bookmarked" => false,
+      "card" => nil,
+      "content" => "foobar",
+      "created_at" => "2020-04-07T19:48:51.000Z",
+      "emojis" => [],
+      "favourited" => false,
+      "favourites_count" => 0,
+      "id" => "9toJCu5YZW7O7gfvH6",
+      "in_reply_to_account_id" => nil,
+      "in_reply_to_id" => nil,
+      "language" => nil,
+      "media_attachments" => [],
+      "mentions" => [],
+      "muted" => false,
+      "pinned" => false,
+      "pleroma" => %{
+        "content" => %{"text/plain" => "foobar"},
+        "conversation_id" => 345_972,
+        "direct_conversation_id" => nil,
+        "emoji_reactions" => [],
+        "expires_at" => nil,
+        "in_reply_to_account_acct" => nil,
+        "local" => true,
+        "spoiler_text" => %{"text/plain" => ""},
+        "thread_muted" => false
+      },
+      "poll" => nil,
+      "reblog" => nil,
+      "reblogged" => false,
+      "reblogs_count" => 0,
+      "replies_count" => 0,
+      "sensitive" => false,
+      "spoiler_text" => "",
+      "tags" => [],
+      "uri" => "http://localhost:4001/objects/0f5dad44-0e9e-4610-b377-a2631e499190",
+      "url" => "http://localhost:4001/notice/9toJCu5YZW7O7gfvH6",
+      "visibility" => "private"
+    }
+  })
+end
diff --git a/lib/pleroma/web/api_spec/schemas/visibility_scope.ex b/lib/pleroma/web/api_spec/schemas/visibility_scope.ex
new file mode 100644 (file)
index 0000000..8c81a4d
--- /dev/null
@@ -0,0 +1,14 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Schemas.VisibilityScope do
+  require OpenApiSpex
+
+  OpenApiSpex.schema(%{
+    title: "VisibilityScope",
+    description: "Status visibility",
+    type: :string,
+    enum: ["public", "unlisted", "private", "direct"]
+  })
+end
index c1cd15bb2a37ba98e923f50ab50dc7083995fc9c..244cf2be5fdc313374a0e541c91f4cc37e03bbcd 100644 (file)
@@ -84,14 +84,18 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
     %__MODULE__{draft | attachments: attachments}
   end
 
-  defp in_reply_to(draft) do
-    case Map.get(draft.params, "in_reply_to_status_id") do
-      "" -> draft
-      nil -> draft
-      id -> %__MODULE__{draft | in_reply_to: Activity.get_by_id(id)}
-    end
+  defp in_reply_to(%{params: %{"in_reply_to_status_id" => ""}} = draft), do: draft
+
+  defp in_reply_to(%{params: %{"in_reply_to_status_id" => id}} = draft) when is_binary(id) do
+    %__MODULE__{draft | in_reply_to: Activity.get_by_id(id)}
   end
 
+  defp in_reply_to(%{params: %{"in_reply_to_status_id" => %Activity{} = in_reply_to}} = draft) do
+    %__MODULE__{draft | in_reply_to: in_reply_to}
+  end
+
+  defp in_reply_to(draft), do: draft
+
   defp in_reply_to_conversation(draft) do
     in_reply_to_conversation = Participation.get(draft.params["in_reply_to_conversation_id"])
     %__MODULE__{draft | in_reply_to_conversation: in_reply_to_conversation}
index f50a909aade322b95423e5a646af90b2ce8a63ee..d1efe0c36e0d1a19eb9377d700639c2434ef8bb0 100644 (file)
@@ -86,8 +86,9 @@ defmodule Pleroma.Web.CommonAPI do
     end
   end
 
-  def repeat(id_or_ap_id, user, params \\ %{}) do
-    with {_, %Activity{} = activity} <- {:find_activity, get_by_id_or_ap_id(id_or_ap_id)},
+  def repeat(id, user, params \\ %{}) do
+    with {_, %Activity{data: %{"type" => "Create"}} = activity} <-
+           {:find_activity, Activity.get_by_id(id)},
          object <- Object.normalize(activity),
          announce_activity <- Utils.get_existing_announce(user.ap_id, object),
          public <- public_announce?(object, params) do
@@ -102,8 +103,9 @@ defmodule Pleroma.Web.CommonAPI do
     end
   end
 
-  def unrepeat(id_or_ap_id, user) do
-    with {_, %Activity{} = activity} <- {:find_activity, get_by_id_or_ap_id(id_or_ap_id)} do
+  def unrepeat(id, user) do
+    with {_, %Activity{data: %{"type" => "Create"}} = activity} <-
+           {:find_activity, Activity.get_by_id(id)} do
       object = Object.normalize(activity)
       ActivityPub.unannounce(user, object)
     else
@@ -160,8 +162,9 @@ defmodule Pleroma.Web.CommonAPI do
     end
   end
 
-  def unfavorite(id_or_ap_id, user) do
-    with {_, %Activity{} = activity} <- {:find_activity, get_by_id_or_ap_id(id_or_ap_id)} do
+  def unfavorite(id, user) do
+    with {_, %Activity{data: %{"type" => "Create"}} = activity} <-
+           {:find_activity, Activity.get_by_id(id)} do
       object = Object.normalize(activity)
       ActivityPub.unlike(user, object)
     else
@@ -332,12 +335,12 @@ defmodule Pleroma.Web.CommonAPI do
 
   defp maybe_create_activity_expiration(result, _), do: result
 
-  def pin(id_or_ap_id, %{ap_id: user_ap_id} = user) do
+  def pin(id, %{ap_id: user_ap_id} = user) do
     with %Activity{
            actor: ^user_ap_id,
            data: %{"type" => "Create"},
            object: %Object{data: %{"type" => object_type}}
-         } = activity <- get_by_id_or_ap_id(id_or_ap_id),
+         } = activity <- Activity.get_by_id_with_object(id),
          true <- object_type in ["Note", "Article", "Question"],
          true <- Visibility.is_public?(activity),
          {:ok, _user} <- User.add_pinnned_activity(user, activity) do
@@ -348,8 +351,8 @@ defmodule Pleroma.Web.CommonAPI do
     end
   end
 
-  def unpin(id_or_ap_id, user) do
-    with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
+  def unpin(id, user) do
+    with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id(id),
          {:ok, _user} <- User.remove_pinnned_activity(user, activity) do
       {:ok, activity}
     else
index 7eec5aa096d8af88913e3db9f61e70eed02731ed..945e63e22a7c24be8fcc4142bd2050d4600d69e6 100644 (file)
@@ -22,24 +22,6 @@ defmodule Pleroma.Web.CommonAPI.Utils do
   require Logger
   require Pleroma.Constants
 
-  # This is a hack for twidere.
-  def get_by_id_or_ap_id(id) do
-    activity =
-      with true <- FlakeId.flake_id?(id),
-           %Activity{} = activity <- Activity.get_by_id_with_object(id) do
-        activity
-      else
-        _ -> Activity.get_create_by_object_ap_id_with_object(id)
-      end
-
-    activity &&
-      if activity.data["type"] == "Create" do
-        activity
-      else
-        Activity.get_create_by_object_ap_id_with_object(activity.data["object"])
-      end
-  end
-
   def attachments_from_ids(%{"media_ids" => ids, "descriptions" => desc} = _) do
     attachments_from_ids_descs(ids, desc)
   end
index 4780081b26868bb3ba08f6b508ed14782fbe3999..eb97ae975e32b3c0ff0886e3c75e526cbbdf32cd 100644 (file)
@@ -82,8 +82,9 @@ defmodule Pleroma.Web.ControllerHelper do
     end
   end
 
-  def assign_account_by_id(%{params: %{"id" => id}} = conn, _) do
-    case Pleroma.User.get_cached_by_id(id) do
+  def assign_account_by_id(conn, _) do
+    # TODO: use `conn.params[:id]` only after moving to OpenAPI
+    case Pleroma.User.get_cached_by_id(conn.params[:id] || conn.params["id"]) do
       %Pleroma.User{} = account -> assign(conn, :account, account)
       nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt()
     end
index f39825e08838f2e638f7721afd9ea05be9d5e832..1eedf02d66a823d0cfc62ce60e2dc964b3252679 100644 (file)
@@ -27,6 +27,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
   alias Pleroma.Web.OAuth.Token
   alias Pleroma.Web.TwitterAPI.TwitterAPI
 
+  plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError)
+
   plug(:skip_plug, [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :create)
 
   plug(:skip_plug, EnsurePublicOrAuthenticatedPlug when action in [:show, :statuses])
@@ -88,26 +90,26 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
 
   action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
 
+  defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.AccountOperation
+
   @doc "POST /api/v1/accounts"
-  def create(
-        %{assigns: %{app: app}} = conn,
-        %{"username" => nickname, "password" => _, "agreement" => true} = params
-      ) do
+  def create(%{assigns: %{app: app}, body_params: params} = conn, _params) do
     params =
       params
       |> Map.take([
-        "email",
-        "captcha_solution",
-        "captcha_token",
-        "captcha_answer_data",
-        "token",
-        "password"
+        :email,
+        :bio,
+        :captcha_solution,
+        :captcha_token,
+        :captcha_answer_data,
+        :token,
+        :password,
+        :fullname
       ])
-      |> Map.put("nickname", nickname)
-      |> Map.put("fullname", params["fullname"] || nickname)
-      |> Map.put("bio", params["bio"] || "")
-      |> Map.put("confirm", params["password"])
-      |> Map.put("trusted_app", app.trusted)
+      |> Map.put(:nickname, params.username)
+      |> Map.put(:fullname, Map.get(params, :fullname, params.username))
+      |> Map.put(:confirm, params.password)
+      |> Map.put(:trusted_app, app.trusted)
 
     with :ok <- validate_email_param(params),
          {:ok, user} <- TwitterAPI.register_user(params, need_confirmation: true),
@@ -131,7 +133,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
     render_error(conn, :forbidden, "Invalid credentials")
   end
 
-  defp validate_email_param(%{"email" => _}), do: :ok
+  defp validate_email_param(%{:email => email}) when not is_nil(email), do: :ok
 
   defp validate_email_param(_) do
     case Pleroma.Config.get([:instance, :account_activation_required]) do
@@ -153,7 +155,14 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
   end
 
   @doc "PATCH /api/v1/accounts/update_credentials"
-  def update_credentials(%{assigns: %{user: user}} = conn, params) do
+  def update_credentials(%{assigns: %{user: original_user}, body_params: params} = conn, _params) do
+    user = original_user
+
+    params =
+      params
+      |> Enum.filter(fn {_, value} -> not is_nil(value) end)
+      |> Enum.into(%{})
+
     user_params =
       [
         :no_rich_text,
@@ -169,22 +178,22 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
         :discoverable
       ]
       |> Enum.reduce(%{}, fn key, acc ->
-        add_if_present(acc, params, to_string(key), key, &{:ok, truthy_param?(&1)})
+        add_if_present(acc, params, key, key, &{:ok, truthy_param?(&1)})
       end)
-      |> add_if_present(params, "display_name", :name)
-      |> add_if_present(params, "note", :bio)
-      |> add_if_present(params, "avatar", :avatar)
-      |> add_if_present(params, "header", :banner)
-      |> add_if_present(params, "pleroma_background_image", :background)
+      |> add_if_present(params, :display_name, :name)
+      |> add_if_present(params, :note, :bio)
+      |> add_if_present(params, :avatar, :avatar)
+      |> add_if_present(params, :header, :banner)
+      |> add_if_present(params, :pleroma_background_image, :background)
       |> add_if_present(
         params,
-        "fields_attributes",
+        :fields_attributes,
         :raw_fields,
         &{:ok, normalize_fields_attributes(&1)}
       )
-      |> add_if_present(params, "pleroma_settings_store", :pleroma_settings_store)
-      |> add_if_present(params, "default_scope", :default_scope)
-      |> add_if_present(params, "actor_type", :actor_type)
+      |> add_if_present(params, :pleroma_settings_store, :pleroma_settings_store)
+      |> add_if_present(params, :default_scope, :default_scope)
+      |> add_if_present(params, :actor_type, :actor_type)
 
     changeset = User.update_changeset(user, user_params)
 
@@ -197,7 +206,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
 
   defp add_if_present(map, params, params_field, map_field, value_function \\ &{:ok, &1}) do
     with true <- Map.has_key?(params, params_field),
-         {:ok, new_value} <- value_function.(params[params_field]) do
+         {:ok, new_value} <- value_function.(Map.get(params, params_field)) do
       Map.put(map, map_field, new_value)
     else
       _ -> map
@@ -208,12 +217,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
     if Enum.all?(fields, &is_tuple/1) do
       Enum.map(fields, fn {_, v} -> v end)
     else
-      fields
+      Enum.map(fields, fn
+        %{} = field -> %{"name" => field.name, "value" => field.value}
+        field -> field
+      end)
     end
   end
 
   @doc "GET /api/v1/accounts/relationships"
-  def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+  def relationships(%{assigns: %{user: user}} = conn, %{id: id}) do
     targets = User.get_all_by_ids(List.wrap(id))
 
     render(conn, "relationships.json", user: user, targets: targets)
@@ -223,7 +235,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
   def relationships(%{assigns: %{user: _user}} = conn, _), do: json(conn, [])
 
   @doc "GET /api/v1/accounts/:id"
-  def show(%{assigns: %{user: for_user}} = conn, %{"id" => nickname_or_id}) do
+  def show(%{assigns: %{user: for_user}} = conn, %{id: nickname_or_id}) do
     with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id, for: for_user),
          true <- User.visible_for?(user, for_user) do
       render(conn, "show.json", user: user, for: for_user)
@@ -234,12 +246,14 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
 
   @doc "GET /api/v1/accounts/:id/statuses"
   def statuses(%{assigns: %{user: reading_user}} = conn, params) do
-    with %User{} = user <- User.get_cached_by_nickname_or_id(params["id"], for: reading_user),
+    with %User{} = user <- User.get_cached_by_nickname_or_id(params.id, for: reading_user),
          true <- User.visible_for?(user, reading_user) do
       params =
         params
-        |> Map.put("tag", params["tagged"])
-        |> Map.delete("godmode")
+        |> Map.delete(:tagged)
+        |> Enum.filter(&(not is_nil(&1)))
+        |> Map.new(fn {key, value} -> {to_string(key), value} end)
+        |> Map.put("tag", params[:tagged])
 
       activities = ActivityPub.fetch_user_activities(user, reading_user, params)
 
@@ -259,6 +273,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
 
   @doc "GET /api/v1/accounts/:id/followers"
   def followers(%{assigns: %{user: for_user, account: user}} = conn, params) do
+    params =
+      params
+      |> Enum.map(fn {key, value} -> {to_string(key), value} end)
+      |> Enum.into(%{})
+
     followers =
       cond do
         for_user && user.id == for_user.id -> MastodonAPI.get_followers(user, params)
@@ -273,6 +292,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
 
   @doc "GET /api/v1/accounts/:id/following"
   def following(%{assigns: %{user: for_user, account: user}} = conn, params) do
+    params =
+      params
+      |> Enum.map(fn {key, value} -> {to_string(key), value} end)
+      |> Enum.into(%{})
+
     followers =
       cond do
         for_user && user.id == for_user.id -> MastodonAPI.get_friends(user, params)
@@ -299,8 +323,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
     {:error, "Can not follow yourself"}
   end
 
-  def follow(%{assigns: %{user: follower, account: followed}} = conn, _params) do
-    with {:ok, follower} <- MastodonAPI.follow(follower, followed, conn.params) do
+  def follow(%{assigns: %{user: follower, account: followed}} = conn, params) do
+    with {:ok, follower} <- MastodonAPI.follow(follower, followed, params) do
       render(conn, "relationship.json", user: follower, target: followed)
     else
       {:error, message} -> json_response(conn, :forbidden, %{error: message})
@@ -319,10 +343,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
   end
 
   @doc "POST /api/v1/accounts/:id/mute"
-  def mute(%{assigns: %{user: muter, account: muted}} = conn, params) do
-    notifications? = params |> Map.get("notifications", true) |> truthy_param?()
-
-    with {:ok, _user_relationships} <- User.mute(muter, muted, notifications?) do
+  def mute(%{assigns: %{user: muter, account: muted}, body_params: params} = conn, _params) do
+    with {:ok, _user_relationships} <- User.mute(muter, muted, params.notifications) do
       render(conn, "relationship.json", user: muter, target: muted)
     else
       {:error, message} -> json_response(conn, :forbidden, %{error: message})
@@ -359,7 +381,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
   end
 
   @doc "POST /api/v1/follows"
-  def follow_by_uri(conn, %{"uri" => uri}) do
+  def follow_by_uri(%{body_params: %{uri: uri}} = conn, _) do
     case User.get_cached_by_nickname(uri) do
       %User{} = user ->
         conn
index 45601ff5992733c63f9004e299aa4c754d2423ed..9eea2e9eb1b4fd65cec5ceb5787d4317ca00f800 100644 (file)
@@ -127,7 +127,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
   def create(
         %{assigns: %{user: user}} = conn,
         %{"status" => _, "scheduled_at" => scheduled_at} = params
-      ) do
+      )
+      when not is_nil(scheduled_at) do
     params = Map.put(params, "in_reply_to_status_id", params["in_reply_to_id"])
 
     with {:far_enough, true} <- {:far_enough, ScheduledActivity.far_enough?(scheduled_at)},
index fb6b18ed505b1304ae272107d7cade34fc4ed21a..2d67e19da616f143e88beaa7da5590d558f2a90f 100644 (file)
@@ -44,6 +44,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
       |> Map.put("type", ["Create", "Announce"])
       |> Map.put("blocking_user", user)
       |> Map.put("muting_user", user)
+      |> Map.put("reply_filtering_user", user)
       |> Map.put("user", user)
 
     recipients = [user.ap_id | User.following(user)]
@@ -109,6 +110,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
         |> Map.put("local_only", local_only)
         |> Map.put("blocking_user", user)
         |> Map.put("muting_user", user)
+        |> Map.put("reply_filtering_user", user)
         |> ActivityPub.fetch_public_activities()
 
       conn
index b5850e1ae8aa1a9a1320faf22e876c4cc63001b5..24167f66f76f4f8a614b8dbc6439ee2456692b4e 100644 (file)
@@ -45,7 +45,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
     end)
   end
 
-  defp get_user(ap_id) do
+  def get_user(ap_id, fake_record_fallback \\ true) do
     cond do
       user = User.get_cached_by_ap_id(ap_id) ->
         user
@@ -53,8 +53,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
       user = User.get_by_guessed_nickname(ap_id) ->
         user
 
-      true ->
+      fake_record_fallback ->
+        # TODO: refactor (fake records is never a good idea)
         User.error_user(ap_id)
+
+      true ->
+        nil
     end
   end
 
@@ -97,7 +101,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
           UserRelationship.view_relationships_option(nil, [])
 
         true ->
-          actors = Enum.map(activities ++ parent_activities, &get_user(&1.data["actor"]))
+          # Note: unresolved users are filtered out
+          actors =
+            (activities ++ parent_activities)
+            |> Enum.map(&get_user(&1.data["actor"], false))
+            |> Enum.filter(& &1)
 
           UserRelationship.view_relationships_option(reading_user, actors,
             source_mutes_only: opts[:skip_relationships]
@@ -521,11 +529,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
   """
   @spec build_tags(list(any())) :: list(map())
   def build_tags(object_tags) when is_list(object_tags) do
-    object_tags = for tag when is_binary(tag) <- object_tags, do: tag
-
-    Enum.reduce(object_tags, [], fn tag, tags ->
-      tags ++ [%{name: tag, url: "/tag/#{URI.encode(tag)}"}]
-    end)
+    object_tags
+    |> Enum.filter(&is_binary/1)
+    |> Enum.map(&%{name: &1, url: "/tag/#{URI.encode(&1)}"})
   end
 
   def build_tags(_), do: []
index 04d823b362a7d807972340330c702f628f9102c9..1ed6ee521a41a77a3eedde98661dc3e5cfba48b4 100644 (file)
@@ -14,7 +14,7 @@ defmodule Pleroma.Web.MongooseIM.MongooseIMController do
   plug(RateLimiter, [name: :authentication, params: ["user"]] when action == :check_password)
 
   def user_exists(conn, %{"user" => username}) do
-    with %User{} <- Repo.get_by(User, nickname: username, local: true) do
+    with %User{} <- Repo.get_by(User, nickname: username, local: true, deactivated: false) do
       conn
       |> json(true)
     else
@@ -26,7 +26,7 @@ defmodule Pleroma.Web.MongooseIM.MongooseIMController do
   end
 
   def check_password(conn, %{"user" => username, "pass" => password}) do
-    with %User{password_hash: password_hash} <-
+    with %User{password_hash: password_hash, deactivated: false} <-
            Repo.get_by(User, nickname: username, local: true),
          true <- Pbkdf2.checkpw(password, password_hash) do
       conn
index 1023f16d4911cb0fc3c4b8334d1b6cf9cd742300..6f06f1431587388d8c923a3e7ec852314d666b94 100644 (file)
@@ -17,12 +17,8 @@ defmodule Pleroma.Web.OAuth.Scopes do
   """
   @spec fetch_scopes(map() | struct(), list()) :: list()
 
-  def fetch_scopes(%Pleroma.Web.ApiSpec.Schemas.AppCreateRequest{scopes: scopes}, default) do
-    parse_scopes(scopes, default)
-  end
-
   def fetch_scopes(params, default) do
-    parse_scopes(params["scope"] || params["scopes"], default)
+    parse_scopes(params["scope"] || params["scopes"] || params[:scopes], default)
   end
 
   def parse_scopes(scopes, _default) when is_list(scopes) do
index 7a1ba6936e3758fe1ebfc259b0f97199a361adc9..cf1d9c74c00ab9f60f1d10e73bb129e99c114430 100644 (file)
@@ -12,73 +12,57 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
   require Pleroma.Constants
 
   def register_user(params, opts \\ []) do
-    token = params["token"]
-    trusted_app? = params["trusted_app"]
-
-    params = %{
-      nickname: params["nickname"],
-      name: params["fullname"],
-      bio: User.parse_bio(params["bio"]),
-      email: params["email"],
-      password: params["password"],
-      password_confirmation: params["confirm"],
-      captcha_solution: params["captcha_solution"],
-      captcha_token: params["captcha_token"],
-      captcha_answer_data: params["captcha_answer_data"]
-    }
-
-    captcha_enabled = Pleroma.Config.get([Pleroma.Captcha, :enabled])
-    # true if captcha is disabled or enabled and valid, false otherwise
-    captcha_ok =
-      if trusted_app? || not captcha_enabled do
-        :ok
-      else
-        Pleroma.Captcha.validate(
-          params[:captcha_token],
-          params[:captcha_solution],
-          params[:captcha_answer_data]
-        )
-      end
-
-    # Captcha invalid
-    if captcha_ok != :ok do
-      {:error, error} = captcha_ok
-      # I have no idea how this error handling works
-      {:error, %{error: Jason.encode!(%{captcha: [error]})}}
-    else
-      registration_process(
-        params,
-        %{
-          registrations_open: Pleroma.Config.get([:instance, :registrations_open]),
-          token: token
-        },
-        opts
-      )
+    params =
+      params
+      |> Map.take([
+        :nickname,
+        :password,
+        :captcha_solution,
+        :captcha_token,
+        :captcha_answer_data,
+        :token,
+        :email,
+        :trusted_app
+      ])
+      |> Map.put(:bio, User.parse_bio(params[:bio] || ""))
+      |> Map.put(:name, params.fullname)
+      |> Map.put(:password_confirmation, params[:confirm])
+
+    case validate_captcha(params) do
+      :ok ->
+        if Pleroma.Config.get([:instance, :registrations_open]) do
+          create_user(params, opts)
+        else
+          create_user_with_invite(params, opts)
+        end
+
+      {:error, error} ->
+        # I have no idea how this error handling works
+        {:error, %{error: Jason.encode!(%{captcha: [error]})}}
     end
   end
 
-  defp registration_process(params, %{registrations_open: true}, opts) do
-    create_user(params, opts)
+  defp validate_captcha(params) do
+    if params[:trusted_app] || not Pleroma.Config.get([Pleroma.Captcha, :enabled]) do
+      :ok
+    else
+      Pleroma.Captcha.validate(
+        params.captcha_token,
+        params.captcha_solution,
+        params.captcha_answer_data
+      )
+    end
   end
 
-  defp registration_process(params, %{token: token}, opts) do
-    invite =
-      unless is_nil(token) do
-        Repo.get_by(UserInviteToken, %{token: token})
-      end
-
-    valid_invite? = invite && UserInviteToken.valid_invite?(invite)
-
-    case invite do
-      nil ->
-        {:error, "Invalid token"}
-
-      invite when valid_invite? ->
-        UserInviteToken.update_usage!(invite)
-        create_user(params, opts)
-
-      _ ->
-        {:error, "Expired token"}
+  defp create_user_with_invite(params, opts) do
+    with %{token: token} when is_binary(token) <- params,
+         %UserInviteToken{} = invite <- Repo.get_by(UserInviteToken, %{token: token}),
+         true <- UserInviteToken.valid_invite?(invite) do
+      UserInviteToken.update_usage!(invite)
+      create_user(params, opts)
+    else
+      nil -> {:error, "Invalid token"}
+      _ -> {:error, "Expired token"}
     end
   end
 
diff --git a/mix.exs b/mix.exs
index b76aef18075f666976edb0c04d10c3bcf8678616..beb05aab9895d4ea016d2a354a91e85af17d2f33 100644 (file)
--- a/mix.exs
+++ b/mix.exs
@@ -189,7 +189,9 @@ defmodule Pleroma.Mixfile do
        ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"},
       {:mox, "~> 0.5", only: :test},
       {:restarter, path: "./restarter"},
-      {:open_api_spex, "~> 3.6"}
+      {:open_api_spex,
+       git: "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git",
+       ref: "b862ebd78de0df95875cf46feb6e9607130dc2a8"}
     ] ++ oauth_deps()
   end
 
index 2b9c545486cbf2d8a60ec9512bf72865292ae629..ee9d93bfbb954767e57b8d5a409ce8a5f0507aad 100644 (file)
--- a/mix.lock
+++ b/mix.lock
@@ -74,7 +74,7 @@
   "nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm", "589b5af56f4afca65217a1f3eb3fee7e79b09c40c742fddc1c312b3ac0b3399f"},
   "nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]},
   "oban": {:hex, :oban, "1.2.0", "7cca94d341be43d220571e28f69131c4afc21095b25257397f50973d3fc59b07", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ba5f8b3f7d76967b3e23cf8014f6a13e4ccb33431e4808f036709a7f822362ee"},
-  "open_api_spex": {:hex, :open_api_spex, "3.6.0", "64205aba9f2607f71b08fd43e3351b9c5e9898ec5ef49fc0ae35890da502ade9", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.1", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "126ba3473966277132079cb1d5bf1e3df9e36fe2acd00166e75fd125cecb59c5"},
+  "open_api_spex": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git", "b862ebd78de0df95875cf46feb6e9607130dc2a8", [ref: "b862ebd78de0df95875cf46feb6e9607130dc2a8"]},
   "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"},
   "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.4", "8dd29ed783f2e12195d7e0a4640effc0a7c37e6537da491f1db01839eee6d053", [:mix], [], "hexpm", "595d09db74cb093b1903381c9de423276a931a2480a46a1a5dc7f932a2a6375b"},
   "phoenix": {:hex, :phoenix, "1.4.13", "67271ad69b51f3719354604f4a3f968f83aa61c19199343656c9caee057ff3b8", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ab765a0feddb81fc62e2116c827b5f068df85159c162bee760745276ad7ddc1b"},
similarity index 86%
rename from test/stat_test.exs
rename to test/stats_test.exs
index bccc1c8d07a435bb5d2df6c07d4507a2deb80f5c..c1aeb2c7f525871c569ab6aa8f59b93233c33102 100644 (file)
@@ -2,11 +2,21 @@
 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
 # SPDX-License-Identifier: AGPL-3.0-only
 
-defmodule Pleroma.StateTest do
+defmodule Pleroma.StatsTest do
   use Pleroma.DataCase
   import Pleroma.Factory
   alias Pleroma.Web.CommonAPI
 
+  describe "user count" do
+    test "it ignores internal users" do
+      _user = insert(:user, local: true)
+      _internal = insert(:user, local: true, nickname: nil)
+      _internal = Pleroma.Web.ActivityPub.Relay.get_actor()
+
+      assert match?(%{stats: %{user_count: 1}}, Pleroma.Stats.calculate_stat_data())
+    end
+  end
+
   describe "status visibility count" do
     test "on new status" do
       user = insert(:user)
diff --git a/test/support/api_spec_helpers.ex b/test/support/api_spec_helpers.ex
new file mode 100644 (file)
index 0000000..80c69c7
--- /dev/null
@@ -0,0 +1,57 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Tests.ApiSpecHelpers do
+  @moduledoc """
+  OpenAPI spec test helpers
+  """
+
+  import ExUnit.Assertions
+
+  alias OpenApiSpex.Cast.Error
+  alias OpenApiSpex.Reference
+  alias OpenApiSpex.Schema
+
+  def assert_schema(value, schema) do
+    api_spec = Pleroma.Web.ApiSpec.spec()
+
+    case OpenApiSpex.cast_value(value, schema, api_spec) do
+      {:ok, data} ->
+        data
+
+      {:error, errors} ->
+        errors =
+          Enum.map(errors, fn error ->
+            message = Error.message(error)
+            path = Error.path_to_string(error)
+            "#{message} at #{path}"
+          end)
+
+        flunk(
+          "Value does not conform to schema #{schema.title}: #{Enum.join(errors, "\n")}\n#{
+            inspect(value)
+          }"
+        )
+    end
+  end
+
+  def resolve_schema(%Schema{} = schema), do: schema
+
+  def resolve_schema(%Reference{} = ref) do
+    schemas = Pleroma.Web.ApiSpec.spec().components.schemas
+    Reference.resolve_schema(ref, schemas)
+  end
+
+  def api_operations do
+    paths = Pleroma.Web.ApiSpec.spec().paths
+
+    Enum.flat_map(paths, fn {_, path_item} ->
+      path_item
+      |> Map.take([:delete, :get, :head, :options, :patch, :post, :put, :trace])
+      |> Map.values()
+      |> Enum.reject(&is_nil/1)
+      |> Enum.uniq()
+    end)
+  end
+end
index 06487420158ebed5412c15aa05800f4823207e6e..fa30a0c41f109f2151e4285d8d26a19267ebe477 100644 (file)
@@ -51,6 +51,60 @@ defmodule Pleroma.Web.ConnCase do
         %{user: user, token: token, conn: conn}
       end
 
+      defp request_content_type(%{conn: conn}) do
+        conn = put_req_header(conn, "content-type", "multipart/form-data")
+        [conn: conn]
+      end
+
+      defp json_response_and_validate_schema(
+             %{
+               private: %{
+                 open_api_spex: %{operation_id: op_id, operation_lookup: lookup, spec: spec}
+               }
+             } = conn,
+             status
+           ) do
+        content_type =
+          conn
+          |> Plug.Conn.get_resp_header("content-type")
+          |> List.first()
+          |> String.split(";")
+          |> List.first()
+
+        status = Plug.Conn.Status.code(status)
+
+        unless lookup[op_id].responses[status] do
+          err = "Response schema not found for #{conn.status} #{conn.method} #{conn.request_path}"
+          flunk(err)
+        end
+
+        schema = lookup[op_id].responses[status].content[content_type].schema
+        json = json_response(conn, status)
+
+        case OpenApiSpex.cast_value(json, schema, spec) do
+          {:ok, _data} ->
+            json
+
+          {:error, errors} ->
+            errors =
+              Enum.map(errors, fn error ->
+                message = OpenApiSpex.Cast.Error.message(error)
+                path = OpenApiSpex.Cast.Error.path_to_string(error)
+                "#{message} at #{path}"
+              end)
+
+            flunk(
+              "Response does not conform to schema of #{op_id} operation: #{
+                Enum.join(errors, "\n")
+              }\n#{inspect(json)}"
+            )
+        end
+      end
+
+      defp json_response_and_validate_schema(conn, _status) do
+        flunk("Response schema not found for #{conn.method} #{conn.request_path} #{conn.status}")
+      end
+
       defp ensure_federating_or_authenticated(conn, url, user) do
         initial_setting = Config.get([:instance, :federating])
         on_exit(fn -> Config.put([:instance, :federating], initial_setting) end)
index 6410df49bb4d78a5e16e3b2ff217cc5014c0a33d..edd7dfb22d231bb6e12316e94278a899dbadfec2 100644 (file)
@@ -994,72 +994,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
     end
   end
 
-  describe "like an object" do
-    test_with_mock "sends an activity to federation", Federator, [:passthrough], [] do
-      Config.put([:instance, :federating], true)
-      note_activity = insert(:note_activity)
-      assert object_activity = Object.normalize(note_activity)
-
-      user = insert(:user)
-
-      {:ok, like_activity, _object} = ActivityPub.like(user, object_activity)
-      assert called(Federator.publish(like_activity))
-    end
-
-    test "returns exist activity if object already liked" do
-      note_activity = insert(:note_activity)
-      assert object_activity = Object.normalize(note_activity)
-
-      user = insert(:user)
-
-      {:ok, like_activity, _object} = ActivityPub.like(user, object_activity)
-
-      {:ok, like_activity_exist, _object} = ActivityPub.like(user, object_activity)
-      assert like_activity == like_activity_exist
-    end
-
-    test "reverts like activity on error" do
-      note_activity = insert(:note_activity)
-      object = Object.normalize(note_activity)
-      user = insert(:user)
-
-      with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
-        assert {:error, :reverted} = ActivityPub.like(user, object)
-      end
-
-      assert Repo.aggregate(Activity, :count, :id) == 1
-      assert Repo.get(Object, object.id) == object
-    end
-
-    test "adds a like activity to the db" do
-      note_activity = insert(:note_activity)
-      assert object = Object.normalize(note_activity)
-
-      user = insert(:user)
-      user_two = insert(:user)
-
-      {:ok, like_activity, object} = ActivityPub.like(user, object)
-
-      assert like_activity.data["actor"] == user.ap_id
-      assert like_activity.data["type"] == "Like"
-      assert like_activity.data["object"] == object.data["id"]
-      assert like_activity.data["to"] == [User.ap_followers(user), note_activity.data["actor"]]
-      assert like_activity.data["context"] == object.data["context"]
-      assert object.data["like_count"] == 1
-      assert object.data["likes"] == [user.ap_id]
-
-      # Just return the original activity if the user already liked it.
-      {:ok, same_like_activity, object} = ActivityPub.like(user, object)
-
-      assert like_activity == same_like_activity
-      assert object.data["likes"] == [user.ap_id]
-      assert object.data["like_count"] == 1
-
-      {:ok, _like_activity, object} = ActivityPub.like(user_two, object)
-      assert object.data["like_count"] == 2
-    end
-  end
-
   describe "unliking" do
     test_with_mock "sends an activity to federation", Federator, [:passthrough], [] do
       Config.put([:instance, :federating], true)
@@ -1071,7 +1005,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
       {:ok, object} = ActivityPub.unlike(user, object)
       refute called(Federator.publish())
 
-      {:ok, _like_activity, object} = ActivityPub.like(user, object)
+      {:ok, _like_activity} = CommonAPI.favorite(user, note_activity.id)
+      object = Object.get_by_id(object.id)
       assert object.data["like_count"] == 1
 
       {:ok, unlike_activity, _, object} = ActivityPub.unlike(user, object)
@@ -1082,10 +1017,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
 
     test "reverts unliking on error" do
       note_activity = insert(:note_activity)
-      object = Object.normalize(note_activity)
       user = insert(:user)
 
-      {:ok, like_activity, object} = ActivityPub.like(user, object)
+      {:ok, like_activity} = CommonAPI.favorite(user, note_activity.id)
+      object = Object.normalize(note_activity)
       assert object.data["like_count"] == 1
 
       with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
@@ -1106,7 +1041,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
       {:ok, object} = ActivityPub.unlike(user, object)
       assert object.data["like_count"] == 0
 
-      {:ok, like_activity, object} = ActivityPub.like(user, object)
+      {:ok, like_activity} = CommonAPI.favorite(user, note_activity.id)
+
+      object = Object.get_by_id(object.id)
       assert object.data["like_count"] == 1
 
       {:ok, unlike_activity, _, object} = ActivityPub.unlike(user, object)
@@ -1973,4 +1910,497 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
                ActivityPub.move(old_user, new_user)
     end
   end
+
+  test "doesn't retrieve replies activities with exclude_replies" do
+    user = insert(:user)
+
+    {:ok, activity} = CommonAPI.post(user, %{"status" => "yeah"})
+
+    {:ok, _reply} =
+      CommonAPI.post(user, %{"status" => "yeah", "in_reply_to_status_id" => activity.id})
+
+    [result] = ActivityPub.fetch_public_activities(%{"exclude_replies" => "true"})
+
+    assert result.id == activity.id
+
+    assert length(ActivityPub.fetch_public_activities()) == 2
+  end
+
+  describe "replies filtering with public messages" do
+    setup :public_messages
+
+    test "public timeline", %{users: %{u1: user}} do
+      activities_ids =
+        %{}
+        |> Map.put("type", ["Create", "Announce"])
+        |> Map.put("local_only", false)
+        |> Map.put("blocking_user", user)
+        |> Map.put("muting_user", user)
+        |> Map.put("reply_filtering_user", user)
+        |> ActivityPub.fetch_public_activities()
+        |> Enum.map(& &1.id)
+
+      assert length(activities_ids) == 16
+    end
+
+    test "public timeline with reply_visibility `following`", %{
+      users: %{u1: user},
+      u1: u1,
+      u2: u2,
+      u3: u3,
+      u4: u4,
+      activities: activities
+    } do
+      activities_ids =
+        %{}
+        |> Map.put("type", ["Create", "Announce"])
+        |> Map.put("local_only", false)
+        |> Map.put("blocking_user", user)
+        |> Map.put("muting_user", user)
+        |> Map.put("reply_visibility", "following")
+        |> Map.put("reply_filtering_user", user)
+        |> ActivityPub.fetch_public_activities()
+        |> Enum.map(& &1.id)
+
+      assert length(activities_ids) == 14
+
+      visible_ids =
+        Map.values(u1) ++ Map.values(u2) ++ Map.values(u4) ++ Map.values(activities) ++ [u3[:r1]]
+
+      assert Enum.all?(visible_ids, &(&1 in activities_ids))
+    end
+
+    test "public timeline with reply_visibility `self`", %{
+      users: %{u1: user},
+      u1: u1,
+      u2: u2,
+      u3: u3,
+      u4: u4,
+      activities: activities
+    } do
+      activities_ids =
+        %{}
+        |> Map.put("type", ["Create", "Announce"])
+        |> Map.put("local_only", false)
+        |> Map.put("blocking_user", user)
+        |> Map.put("muting_user", user)
+        |> Map.put("reply_visibility", "self")
+        |> Map.put("reply_filtering_user", user)
+        |> ActivityPub.fetch_public_activities()
+        |> Enum.map(& &1.id)
+
+      assert length(activities_ids) == 10
+      visible_ids = Map.values(u1) ++ [u2[:r1], u3[:r1], u4[:r1]] ++ Map.values(activities)
+      assert Enum.all?(visible_ids, &(&1 in activities_ids))
+    end
+
+    test "home timeline", %{
+      users: %{u1: user},
+      activities: activities,
+      u1: u1,
+      u2: u2,
+      u3: u3,
+      u4: u4
+    } do
+      params =
+        %{}
+        |> Map.put("type", ["Create", "Announce"])
+        |> Map.put("blocking_user", user)
+        |> Map.put("muting_user", user)
+        |> Map.put("user", user)
+        |> Map.put("reply_filtering_user", user)
+
+      activities_ids =
+        ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
+        |> Enum.map(& &1.id)
+
+      assert length(activities_ids) == 13
+
+      visible_ids =
+        Map.values(u1) ++
+          Map.values(u3) ++
+          [
+            activities[:a1],
+            activities[:a2],
+            activities[:a4],
+            u2[:r1],
+            u2[:r3],
+            u4[:r1],
+            u4[:r2]
+          ]
+
+      assert Enum.all?(visible_ids, &(&1 in activities_ids))
+    end
+
+    test "home timeline with reply_visibility `following`", %{
+      users: %{u1: user},
+      activities: activities,
+      u1: u1,
+      u2: u2,
+      u3: u3,
+      u4: u4
+    } do
+      params =
+        %{}
+        |> Map.put("type", ["Create", "Announce"])
+        |> Map.put("blocking_user", user)
+        |> Map.put("muting_user", user)
+        |> Map.put("user", user)
+        |> Map.put("reply_visibility", "following")
+        |> Map.put("reply_filtering_user", user)
+
+      activities_ids =
+        ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
+        |> Enum.map(& &1.id)
+
+      assert length(activities_ids) == 11
+
+      visible_ids =
+        Map.values(u1) ++
+          [
+            activities[:a1],
+            activities[:a2],
+            activities[:a4],
+            u2[:r1],
+            u2[:r3],
+            u3[:r1],
+            u4[:r1],
+            u4[:r2]
+          ]
+
+      assert Enum.all?(visible_ids, &(&1 in activities_ids))
+    end
+
+    test "home timeline with reply_visibility `self`", %{
+      users: %{u1: user},
+      activities: activities,
+      u1: u1,
+      u2: u2,
+      u3: u3,
+      u4: u4
+    } do
+      params =
+        %{}
+        |> Map.put("type", ["Create", "Announce"])
+        |> Map.put("blocking_user", user)
+        |> Map.put("muting_user", user)
+        |> Map.put("user", user)
+        |> Map.put("reply_visibility", "self")
+        |> Map.put("reply_filtering_user", user)
+
+      activities_ids =
+        ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
+        |> Enum.map(& &1.id)
+
+      assert length(activities_ids) == 9
+
+      visible_ids =
+        Map.values(u1) ++
+          [
+            activities[:a1],
+            activities[:a2],
+            activities[:a4],
+            u2[:r1],
+            u3[:r1],
+            u4[:r1]
+          ]
+
+      assert Enum.all?(visible_ids, &(&1 in activities_ids))
+    end
+  end
+
+  describe "replies filtering with private messages" do
+    setup :private_messages
+
+    test "public timeline", %{users: %{u1: user}} do
+      activities_ids =
+        %{}
+        |> Map.put("type", ["Create", "Announce"])
+        |> Map.put("local_only", false)
+        |> Map.put("blocking_user", user)
+        |> Map.put("muting_user", user)
+        |> Map.put("user", user)
+        |> ActivityPub.fetch_public_activities()
+        |> Enum.map(& &1.id)
+
+      assert activities_ids == []
+    end
+
+    test "public timeline with default reply_visibility `following`", %{users: %{u1: user}} do
+      activities_ids =
+        %{}
+        |> Map.put("type", ["Create", "Announce"])
+        |> Map.put("local_only", false)
+        |> Map.put("blocking_user", user)
+        |> Map.put("muting_user", user)
+        |> Map.put("reply_visibility", "following")
+        |> Map.put("reply_filtering_user", user)
+        |> Map.put("user", user)
+        |> ActivityPub.fetch_public_activities()
+        |> Enum.map(& &1.id)
+
+      assert activities_ids == []
+    end
+
+    test "public timeline with default reply_visibility `self`", %{users: %{u1: user}} do
+      activities_ids =
+        %{}
+        |> Map.put("type", ["Create", "Announce"])
+        |> Map.put("local_only", false)
+        |> Map.put("blocking_user", user)
+        |> Map.put("muting_user", user)
+        |> Map.put("reply_visibility", "self")
+        |> Map.put("reply_filtering_user", user)
+        |> Map.put("user", user)
+        |> ActivityPub.fetch_public_activities()
+        |> Enum.map(& &1.id)
+
+      assert activities_ids == []
+    end
+
+    test "home timeline", %{users: %{u1: user}} do
+      params =
+        %{}
+        |> Map.put("type", ["Create", "Announce"])
+        |> Map.put("blocking_user", user)
+        |> Map.put("muting_user", user)
+        |> Map.put("user", user)
+
+      activities_ids =
+        ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
+        |> Enum.map(& &1.id)
+
+      assert length(activities_ids) == 12
+    end
+
+    test "home timeline with default reply_visibility `following`", %{users: %{u1: user}} do
+      params =
+        %{}
+        |> Map.put("type", ["Create", "Announce"])
+        |> Map.put("blocking_user", user)
+        |> Map.put("muting_user", user)
+        |> Map.put("user", user)
+        |> Map.put("reply_visibility", "following")
+        |> Map.put("reply_filtering_user", user)
+
+      activities_ids =
+        ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
+        |> Enum.map(& &1.id)
+
+      assert length(activities_ids) == 12
+    end
+
+    test "home timeline with default reply_visibility `self`", %{
+      users: %{u1: user},
+      activities: activities,
+      u1: u1,
+      u2: u2,
+      u3: u3,
+      u4: u4
+    } do
+      params =
+        %{}
+        |> Map.put("type", ["Create", "Announce"])
+        |> Map.put("blocking_user", user)
+        |> Map.put("muting_user", user)
+        |> Map.put("user", user)
+        |> Map.put("reply_visibility", "self")
+        |> Map.put("reply_filtering_user", user)
+
+      activities_ids =
+        ActivityPub.fetch_activities([user.ap_id | User.following(user)], params)
+        |> Enum.map(& &1.id)
+
+      assert length(activities_ids) == 10
+
+      visible_ids =
+        Map.values(u1) ++ Map.values(u4) ++ [u2[:r1], u3[:r1]] ++ Map.values(activities)
+
+      assert Enum.all?(visible_ids, &(&1 in activities_ids))
+    end
+  end
+
+  defp public_messages(_) do
+    [u1, u2, u3, u4] = insert_list(4, :user)
+    {:ok, u1} = User.follow(u1, u2)
+    {:ok, u2} = User.follow(u2, u1)
+    {:ok, u1} = User.follow(u1, u4)
+    {:ok, u4} = User.follow(u4, u1)
+
+    {:ok, u2} = User.follow(u2, u3)
+    {:ok, u3} = User.follow(u3, u2)
+
+    {:ok, a1} = CommonAPI.post(u1, %{"status" => "Status"})
+
+    {:ok, r1_1} =
+      CommonAPI.post(u2, %{
+        "status" => "@#{u1.nickname} reply from u2 to u1",
+        "in_reply_to_status_id" => a1.id
+      })
+
+    {:ok, r1_2} =
+      CommonAPI.post(u3, %{
+        "status" => "@#{u1.nickname} reply from u3 to u1",
+        "in_reply_to_status_id" => a1.id
+      })
+
+    {:ok, r1_3} =
+      CommonAPI.post(u4, %{
+        "status" => "@#{u1.nickname} reply from u4 to u1",
+        "in_reply_to_status_id" => a1.id
+      })
+
+    {:ok, a2} = CommonAPI.post(u2, %{"status" => "Status"})
+
+    {:ok, r2_1} =
+      CommonAPI.post(u1, %{
+        "status" => "@#{u2.nickname} reply from u1 to u2",
+        "in_reply_to_status_id" => a2.id
+      })
+
+    {:ok, r2_2} =
+      CommonAPI.post(u3, %{
+        "status" => "@#{u2.nickname} reply from u3 to u2",
+        "in_reply_to_status_id" => a2.id
+      })
+
+    {:ok, r2_3} =
+      CommonAPI.post(u4, %{
+        "status" => "@#{u2.nickname} reply from u4 to u2",
+        "in_reply_to_status_id" => a2.id
+      })
+
+    {:ok, a3} = CommonAPI.post(u3, %{"status" => "Status"})
+
+    {:ok, r3_1} =
+      CommonAPI.post(u1, %{
+        "status" => "@#{u3.nickname} reply from u1 to u3",
+        "in_reply_to_status_id" => a3.id
+      })
+
+    {:ok, r3_2} =
+      CommonAPI.post(u2, %{
+        "status" => "@#{u3.nickname} reply from u2 to u3",
+        "in_reply_to_status_id" => a3.id
+      })
+
+    {:ok, r3_3} =
+      CommonAPI.post(u4, %{
+        "status" => "@#{u3.nickname} reply from u4 to u3",
+        "in_reply_to_status_id" => a3.id
+      })
+
+    {:ok, a4} = CommonAPI.post(u4, %{"status" => "Status"})
+
+    {:ok, r4_1} =
+      CommonAPI.post(u1, %{
+        "status" => "@#{u4.nickname} reply from u1 to u4",
+        "in_reply_to_status_id" => a4.id
+      })
+
+    {:ok, r4_2} =
+      CommonAPI.post(u2, %{
+        "status" => "@#{u4.nickname} reply from u2 to u4",
+        "in_reply_to_status_id" => a4.id
+      })
+
+    {:ok, r4_3} =
+      CommonAPI.post(u3, %{
+        "status" => "@#{u4.nickname} reply from u3 to u4",
+        "in_reply_to_status_id" => a4.id
+      })
+
+    {:ok,
+     users: %{u1: u1, u2: u2, u3: u3, u4: u4},
+     activities: %{a1: a1.id, a2: a2.id, a3: a3.id, a4: a4.id},
+     u1: %{r1: r1_1.id, r2: r1_2.id, r3: r1_3.id},
+     u2: %{r1: r2_1.id, r2: r2_2.id, r3: r2_3.id},
+     u3: %{r1: r3_1.id, r2: r3_2.id, r3: r3_3.id},
+     u4: %{r1: r4_1.id, r2: r4_2.id, r3: r4_3.id}}
+  end
+
+  defp private_messages(_) do
+    [u1, u2, u3, u4] = insert_list(4, :user)
+    {:ok, u1} = User.follow(u1, u2)
+    {:ok, u2} = User.follow(u2, u1)
+    {:ok, u1} = User.follow(u1, u3)
+    {:ok, u3} = User.follow(u3, u1)
+    {:ok, u1} = User.follow(u1, u4)
+    {:ok, u4} = User.follow(u4, u1)
+
+    {:ok, u2} = User.follow(u2, u3)
+    {:ok, u3} = User.follow(u3, u2)
+
+    {:ok, a1} = CommonAPI.post(u1, %{"status" => "Status", "visibility" => "private"})
+
+    {:ok, r1_1} =
+      CommonAPI.post(u2, %{
+        "status" => "@#{u1.nickname} reply from u2 to u1",
+        "in_reply_to_status_id" => a1.id,
+        "visibility" => "private"
+      })
+
+    {:ok, r1_2} =
+      CommonAPI.post(u3, %{
+        "status" => "@#{u1.nickname} reply from u3 to u1",
+        "in_reply_to_status_id" => a1.id,
+        "visibility" => "private"
+      })
+
+    {:ok, r1_3} =
+      CommonAPI.post(u4, %{
+        "status" => "@#{u1.nickname} reply from u4 to u1",
+        "in_reply_to_status_id" => a1.id,
+        "visibility" => "private"
+      })
+
+    {:ok, a2} = CommonAPI.post(u2, %{"status" => "Status", "visibility" => "private"})
+
+    {:ok, r2_1} =
+      CommonAPI.post(u1, %{
+        "status" => "@#{u2.nickname} reply from u1 to u2",
+        "in_reply_to_status_id" => a2.id,
+        "visibility" => "private"
+      })
+
+    {:ok, r2_2} =
+      CommonAPI.post(u3, %{
+        "status" => "@#{u2.nickname} reply from u3 to u2",
+        "in_reply_to_status_id" => a2.id,
+        "visibility" => "private"
+      })
+
+    {:ok, a3} = CommonAPI.post(u3, %{"status" => "Status", "visibility" => "private"})
+
+    {:ok, r3_1} =
+      CommonAPI.post(u1, %{
+        "status" => "@#{u3.nickname} reply from u1 to u3",
+        "in_reply_to_status_id" => a3.id,
+        "visibility" => "private"
+      })
+
+    {:ok, r3_2} =
+      CommonAPI.post(u2, %{
+        "status" => "@#{u3.nickname} reply from u2 to u3",
+        "in_reply_to_status_id" => a3.id,
+        "visibility" => "private"
+      })
+
+    {:ok, a4} = CommonAPI.post(u4, %{"status" => "Status", "visibility" => "private"})
+
+    {:ok, r4_1} =
+      CommonAPI.post(u1, %{
+        "status" => "@#{u4.nickname} reply from u1 to u4",
+        "in_reply_to_status_id" => a4.id,
+        "visibility" => "private"
+      })
+
+    {:ok,
+     users: %{u1: u1, u2: u2, u3: u3, u4: u4},
+     activities: %{a1: a1.id, a2: a2.id, a3: a3.id, a4: a4.id},
+     u1: %{r1: r1_1.id, r2: r1_2.id, r3: r1_3.id},
+     u2: %{r1: r2_1.id, r2: r2_2.id},
+     u3: %{r1: r3_1.id, r2: r3_2.id},
+     u4: %{r1: r4_1.id}}
+  end
 end
index e913a5148a3ac6bcfb730581e5fbbf9a1f514575..b0bfed9178a53cba7a5f034cbe0cc1060d4f487e 100644 (file)
@@ -224,8 +224,7 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do
 
       object = Object.normalize(activity)
       {:ok, [vote], object} = CommonAPI.vote(other_user, object, [0])
-      vote_object = Object.normalize(vote)
-      {:ok, _activity, _object} = ActivityPub.like(user, vote_object)
+      {:ok, _activity} = CommonAPI.favorite(user, activity.id)
       [fetched_vote] = Utils.get_existing_votes(other_user.ap_id, object)
       assert fetched_vote.id == vote.id
     end
@@ -346,7 +345,7 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do
 
       user = insert(:user)
       refute Utils.get_existing_like(user.ap_id, object)
-      {:ok, like_activity, _object} = ActivityPub.like(user, object)
+      {:ok, like_activity} = CommonAPI.favorite(user, note_activity.id)
 
       assert ^like_activity = Utils.get_existing_like(user.ap_id, object)
     end
diff --git a/test/web/api_spec/app_operation_test.exs b/test/web/api_spec/app_operation_test.exs
deleted file mode 100644 (file)
index 5b96abb..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.ApiSpec.AppOperationTest do
-  use Pleroma.Web.ConnCase, async: true
-
-  alias Pleroma.Web.ApiSpec
-  alias Pleroma.Web.ApiSpec.Schemas.AppCreateRequest
-  alias Pleroma.Web.ApiSpec.Schemas.AppCreateResponse
-
-  import OpenApiSpex.TestAssertions
-  import Pleroma.Factory
-
-  test "AppCreateRequest example matches schema" do
-    api_spec = ApiSpec.spec()
-    schema = AppCreateRequest.schema()
-    assert_schema(schema.example, "AppCreateRequest", api_spec)
-  end
-
-  test "AppCreateResponse example matches schema" do
-    api_spec = ApiSpec.spec()
-    schema = AppCreateResponse.schema()
-    assert_schema(schema.example, "AppCreateResponse", api_spec)
-  end
-
-  test "AppController produces a AppCreateResponse", %{conn: conn} do
-    api_spec = ApiSpec.spec()
-    app_attrs = build(:oauth_app)
-
-    json =
-      conn
-      |> put_req_header("content-type", "application/json")
-      |> post(
-        "/api/v1/apps",
-        Jason.encode!(%{
-          client_name: app_attrs.client_name,
-          redirect_uris: app_attrs.redirect_uris
-        })
-      )
-      |> json_response(200)
-
-    assert_schema(json, "AppCreateResponse", api_spec)
-  end
-end
diff --git a/test/web/api_spec/schema_examples_test.exs b/test/web/api_spec/schema_examples_test.exs
new file mode 100644 (file)
index 0000000..88b6f07
--- /dev/null
@@ -0,0 +1,43 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.SchemaExamplesTest do
+  use ExUnit.Case, async: true
+  import Pleroma.Tests.ApiSpecHelpers
+
+  @content_type "application/json"
+
+  for operation <- api_operations() do
+    describe operation.operationId <> " Request Body" do
+      if operation.requestBody do
+        @media_type operation.requestBody.content[@content_type]
+        @schema resolve_schema(@media_type.schema)
+
+        if @media_type.example do
+          test "request body media type example matches schema" do
+            assert_schema(@media_type.example, @schema)
+          end
+        end
+
+        if @schema.example do
+          test "request body schema example matches schema" do
+            assert_schema(@schema.example, @schema)
+          end
+        end
+      end
+    end
+
+    for {status, response} <- operation.responses do
+      describe "#{operation.operationId} - #{status} Response" do
+        @schema resolve_schema(response.content[@content_type].schema)
+
+        if @schema.example do
+          test "example matches schema" do
+            assert_schema(@schema.example, @schema)
+          end
+        end
+      end
+    end
+  end
+end
index e130736ecec21cfe1f475892077bb2d4fabad547..1758662b0c69b3caccd8289a33e4a916f8413dde 100644 (file)
@@ -21,6 +21,60 @@ defmodule Pleroma.Web.CommonAPITest do
   setup do: clear_config([:instance, :limit])
   setup do: clear_config([:instance, :max_pinned_statuses])
 
+  test "favoriting race condition" do
+    user = insert(:user)
+    users_serial = insert_list(10, :user)
+    users = insert_list(10, :user)
+
+    {:ok, activity} = CommonAPI.post(user, %{"status" => "."})
+
+    users_serial
+    |> Enum.map(fn user ->
+      CommonAPI.favorite(user, activity.id)
+    end)
+
+    object = Object.get_by_ap_id(activity.data["object"])
+    assert object.data["like_count"] == 10
+
+    users
+    |> Enum.map(fn user ->
+      Task.async(fn ->
+        CommonAPI.favorite(user, activity.id)
+      end)
+    end)
+    |> Enum.map(&Task.await/1)
+
+    object = Object.get_by_ap_id(activity.data["object"])
+    assert object.data["like_count"] == 20
+  end
+
+  test "repeating race condition" do
+    user = insert(:user)
+    users_serial = insert_list(10, :user)
+    users = insert_list(10, :user)
+
+    {:ok, activity} = CommonAPI.post(user, %{"status" => "."})
+
+    users_serial
+    |> Enum.map(fn user ->
+      CommonAPI.repeat(activity.id, user)
+    end)
+
+    object = Object.get_by_ap_id(activity.data["object"])
+    assert object.data["announcement_count"] == 10
+
+    users
+    |> Enum.map(fn user ->
+      Task.async(fn ->
+        CommonAPI.repeat(activity.id, user)
+      end)
+    end)
+    |> Enum.map(&Task.await/1)
+
+    object = Object.get_by_ap_id(activity.data["object"])
+    assert object.data["announcement_count"] == 20
+  end
+
   test "when replying to a conversation / participation, it will set the correct context id even if no explicit reply_to is given" do
     user = insert(:user)
     {:ok, activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"})
@@ -256,6 +310,16 @@ defmodule Pleroma.Web.CommonAPITest do
       {:ok, %Activity{}, _} = CommonAPI.repeat(activity.id, user)
     end
 
+    test "can't repeat a repeat" do
+      user = insert(:user)
+      other_user = insert(:user)
+      {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
+
+      {:ok, %Activity{} = announce, _} = CommonAPI.repeat(activity.id, other_user)
+
+      refute match?({:ok, %Activity{}, _}, CommonAPI.repeat(announce.id, user))
+    end
+
     test "repeating a status privately" do
       user = insert(:user)
       other_user = insert(:user)
@@ -285,8 +349,8 @@ defmodule Pleroma.Web.CommonAPITest do
       other_user = insert(:user)
 
       {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
-      {:ok, %Activity{} = activity, object} = CommonAPI.repeat(activity.id, user)
-      {:ok, ^activity, ^object} = CommonAPI.repeat(activity.id, user)
+      {:ok, %Activity{} = announce, object} = CommonAPI.repeat(activity.id, user)
+      {:ok, ^announce, ^object} = CommonAPI.repeat(activity.id, user)
     end
 
     test "favoriting a status twice returns ok, but without the like activity" do
@@ -360,7 +424,9 @@ defmodule Pleroma.Web.CommonAPITest do
 
       user = refresh_record(user)
 
-      assert {:ok, ^activity} = CommonAPI.unpin(activity.id, user)
+      id = activity.id
+
+      assert match?({:ok, %{id: ^id}}, CommonAPI.unpin(activity.id, user))
 
       user = refresh_record(user)
 
index b21445fe9a857defd8378176a6d9a496645ff8c7..18a3b3b8761624f34c61478562166f9724fef9d0 100644 (file)
@@ -335,26 +335,6 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do
     end
   end
 
-  describe "get_by_id_or_ap_id/1" do
-    test "get activity by id" do
-      activity = insert(:note_activity)
-      %Pleroma.Activity{} = note = Utils.get_by_id_or_ap_id(activity.id)
-      assert note.id == activity.id
-    end
-
-    test "get activity by ap_id" do
-      activity = insert(:note_activity)
-      %Pleroma.Activity{} = note = Utils.get_by_id_or_ap_id(activity.data["object"])
-      assert note.id == activity.id
-    end
-
-    test "get activity by object when type isn't `Create` " do
-      activity = insert(:like_activity)
-      %Pleroma.Activity{} = like = Utils.get_by_id_or_ap_id(activity.id)
-      assert like.data["object"] == activity.data["object"]
-    end
-  end
-
   describe "to_master_date/1" do
     test "removes microseconds from date (NaiveDateTime)" do
       assert Utils.to_masto_date(~N[2015-01-23 23:50:07.123]) == "2015-01-23T23:50:07.000Z"
index 2d256f63c1f3e5b2284cad219a7f0c181da87f03..fdb6d4c5d7f8d0ffa074025481bfc5c476c2534d 100644 (file)
@@ -14,6 +14,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
 
   describe "updating credentials" do
     setup do: oauth_access(["write:accounts"])
+    setup :request_content_type
 
     test "sets user settings in a generic way", %{conn: conn} do
       res_conn =
@@ -25,7 +26,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
           }
         })
 
-      assert user_data = json_response(res_conn, 200)
+      assert user_data = json_response_and_validate_schema(res_conn, 200)
       assert user_data["pleroma"]["settings_store"] == %{"pleroma_fe" => %{"theme" => "bla"}}
 
       user = Repo.get(User, user_data["id"])
@@ -41,7 +42,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
           }
         })
 
-      assert user_data = json_response(res_conn, 200)
+      assert user_data = json_response_and_validate_schema(res_conn, 200)
 
       assert user_data["pleroma"]["settings_store"] ==
                %{
@@ -62,7 +63,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
           }
         })
 
-      assert user_data = json_response(res_conn, 200)
+      assert user_data = json_response_and_validate_schema(res_conn, 200)
 
       assert user_data["pleroma"]["settings_store"] ==
                %{
@@ -79,7 +80,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
           "note" => "I drink #cofe with @#{user2.nickname}\n\nsuya.."
         })
 
-      assert user_data = json_response(conn, 200)
+      assert user_data = json_response_and_validate_schema(conn, 200)
 
       assert user_data["note"] ==
                ~s(I drink <a class="hashtag" data-tag="cofe" href="http://localhost:4001/tag/cofe">#cofe</a> with <span class="h-card"><a class="u-url mention" data-user="#{
@@ -90,7 +91,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
     test "updates the user's locking status", %{conn: conn} do
       conn = patch(conn, "/api/v1/accounts/update_credentials", %{locked: "true"})
 
-      assert user_data = json_response(conn, 200)
+      assert user_data = json_response_and_validate_schema(conn, 200)
       assert user_data["locked"] == true
     end
 
@@ -100,21 +101,21 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
       conn = patch(conn, "/api/v1/accounts/update_credentials", %{allow_following_move: "false"})
 
       assert refresh_record(user).allow_following_move == false
-      assert user_data = json_response(conn, 200)
+      assert user_data = json_response_and_validate_schema(conn, 200)
       assert user_data["pleroma"]["allow_following_move"] == false
     end
 
     test "updates the user's default scope", %{conn: conn} do
-      conn = patch(conn, "/api/v1/accounts/update_credentials", %{default_scope: "cofe"})
+      conn = patch(conn, "/api/v1/accounts/update_credentials", %{default_scope: "unlisted"})
 
-      assert user_data = json_response(conn, 200)
-      assert user_data["source"]["privacy"] == "cofe"
+      assert user_data = json_response_and_validate_schema(conn, 200)
+      assert user_data["source"]["privacy"] == "unlisted"
     end
 
     test "updates the user's hide_followers status", %{conn: conn} do
       conn = patch(conn, "/api/v1/accounts/update_credentials", %{hide_followers: "true"})
 
-      assert user_data = json_response(conn, 200)
+      assert user_data = json_response_and_validate_schema(conn, 200)
       assert user_data["pleroma"]["hide_followers"] == true
     end
 
@@ -122,12 +123,12 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
       assert %{"source" => %{"pleroma" => %{"discoverable" => true}}} =
                conn
                |> patch("/api/v1/accounts/update_credentials", %{discoverable: "true"})
-               |> json_response(:ok)
+               |> json_response_and_validate_schema(:ok)
 
       assert %{"source" => %{"pleroma" => %{"discoverable" => false}}} =
                conn
                |> patch("/api/v1/accounts/update_credentials", %{discoverable: "false"})
-               |> json_response(:ok)
+               |> json_response_and_validate_schema(:ok)
     end
 
     test "updates the user's hide_followers_count and hide_follows_count", %{conn: conn} do
@@ -137,7 +138,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
           hide_follows_count: "true"
         })
 
-      assert user_data = json_response(conn, 200)
+      assert user_data = json_response_and_validate_schema(conn, 200)
       assert user_data["pleroma"]["hide_followers_count"] == true
       assert user_data["pleroma"]["hide_follows_count"] == true
     end
@@ -146,7 +147,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
       response =
         conn
         |> patch("/api/v1/accounts/update_credentials", %{skip_thread_containment: "true"})
-        |> json_response(200)
+        |> json_response_and_validate_schema(200)
 
       assert response["pleroma"]["skip_thread_containment"] == true
       assert refresh_record(user).skip_thread_containment
@@ -155,28 +156,28 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
     test "updates the user's hide_follows status", %{conn: conn} do
       conn = patch(conn, "/api/v1/accounts/update_credentials", %{hide_follows: "true"})
 
-      assert user_data = json_response(conn, 200)
+      assert user_data = json_response_and_validate_schema(conn, 200)
       assert user_data["pleroma"]["hide_follows"] == true
     end
 
     test "updates the user's hide_favorites status", %{conn: conn} do
       conn = patch(conn, "/api/v1/accounts/update_credentials", %{hide_favorites: "true"})
 
-      assert user_data = json_response(conn, 200)
+      assert user_data = json_response_and_validate_schema(conn, 200)
       assert user_data["pleroma"]["hide_favorites"] == true
     end
 
     test "updates the user's show_role status", %{conn: conn} do
       conn = patch(conn, "/api/v1/accounts/update_credentials", %{show_role: "false"})
 
-      assert user_data = json_response(conn, 200)
+      assert user_data = json_response_and_validate_schema(conn, 200)
       assert user_data["source"]["pleroma"]["show_role"] == false
     end
 
     test "updates the user's no_rich_text status", %{conn: conn} do
       conn = patch(conn, "/api/v1/accounts/update_credentials", %{no_rich_text: "true"})
 
-      assert user_data = json_response(conn, 200)
+      assert user_data = json_response_and_validate_schema(conn, 200)
       assert user_data["source"]["pleroma"]["no_rich_text"] == true
     end
 
@@ -184,7 +185,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
       conn =
         patch(conn, "/api/v1/accounts/update_credentials", %{"display_name" => "markorepairs"})
 
-      assert user_data = json_response(conn, 200)
+      assert user_data = json_response_and_validate_schema(conn, 200)
       assert user_data["display_name"] == "markorepairs"
     end
 
@@ -197,7 +198,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
 
       conn = patch(conn, "/api/v1/accounts/update_credentials", %{"avatar" => new_avatar})
 
-      assert user_response = json_response(conn, 200)
+      assert user_response = json_response_and_validate_schema(conn, 200)
       assert user_response["avatar"] != User.avatar_url(user)
     end
 
@@ -210,7 +211,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
 
       conn = patch(conn, "/api/v1/accounts/update_credentials", %{"header" => new_header})
 
-      assert user_response = json_response(conn, 200)
+      assert user_response = json_response_and_validate_schema(conn, 200)
       assert user_response["header"] != User.banner_url(user)
     end
 
@@ -226,7 +227,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
           "pleroma_background_image" => new_header
         })
 
-      assert user_response = json_response(conn, 200)
+      assert user_response = json_response_and_validate_schema(conn, 200)
       assert user_response["pleroma"]["background_image"]
     end
 
@@ -237,14 +238,15 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
       for token <- [token1, token2] do
         conn =
           build_conn()
+          |> put_req_header("content-type", "multipart/form-data")
           |> put_req_header("authorization", "Bearer #{token.token}")
           |> patch("/api/v1/accounts/update_credentials", %{})
 
         if token == token1 do
           assert %{"error" => "Insufficient permissions: write:accounts."} ==
-                   json_response(conn, 403)
+                   json_response_and_validate_schema(conn, 403)
         else
-          assert json_response(conn, 200)
+          assert json_response_and_validate_schema(conn, 200)
         end
       end
     end
@@ -259,11 +261,11 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
           "display_name" => name
         })
 
-      assert json_response(ret_conn, 200)
+      assert json_response_and_validate_schema(ret_conn, 200)
 
       conn = get(conn, "/api/v1/accounts/#{user.id}")
 
-      assert user_data = json_response(conn, 200)
+      assert user_data = json_response_and_validate_schema(conn, 200)
 
       assert user_data["note"] == note
       assert user_data["display_name"] == name
@@ -279,7 +281,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
       account_data =
         conn
         |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
-        |> json_response(200)
+        |> json_response_and_validate_schema(200)
 
       assert account_data["fields"] == [
                %{"name" => "<a href=\"http://google.com\">foo</a>", "value" => "bar"},
@@ -312,7 +314,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
         conn
         |> put_req_header("content-type", "application/x-www-form-urlencoded")
         |> patch("/api/v1/accounts/update_credentials", fields)
-        |> json_response(200)
+        |> json_response_and_validate_schema(200)
 
       assert account["fields"] == [
                %{"name" => "foo", "value" => "bar"},
@@ -337,7 +339,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
       account =
         conn
         |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
-        |> json_response(200)
+        |> json_response_and_validate_schema(200)
 
       assert account["fields"] == [
                %{"name" => "foo", "value" => ""}
@@ -356,14 +358,14 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
       assert %{"error" => "Invalid request"} ==
                conn
                |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
-               |> json_response(403)
+               |> json_response_and_validate_schema(403)
 
       fields = [%{"name" => long_name, "value" => "bar"}]
 
       assert %{"error" => "Invalid request"} ==
                conn
                |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
-               |> json_response(403)
+               |> json_response_and_validate_schema(403)
 
       Pleroma.Config.put([:instance, :max_account_fields], 1)
 
@@ -375,7 +377,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
       assert %{"error" => "Invalid request"} ==
                conn
                |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
-               |> json_response(403)
+               |> json_response_and_validate_schema(403)
     end
   end
 end
index 8c428efeee0c0114b89b39e5c0c1955751449b6b..ba70ba66c95998c6965710c46de456ab1b15a210 100644 (file)
@@ -19,43 +19,37 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
     setup do: clear_config([:instance, :limit_to_local_content])
 
     test "works by id" do
-      user = insert(:user)
+      %User{id: user_id} = insert(:user)
 
-      conn =
-        build_conn()
-        |> get("/api/v1/accounts/#{user.id}")
-
-      assert %{"id" => id} = json_response(conn, 200)
-      assert id == to_string(user.id)
+      assert %{"id" => ^user_id} =
+               build_conn()
+               |> get("/api/v1/accounts/#{user_id}")
+               |> json_response_and_validate_schema(200)
 
-      conn =
-        build_conn()
-        |> get("/api/v1/accounts/-1")
-
-      assert %{"error" => "Can't find user"} = json_response(conn, 404)
+      assert %{"error" => "Can't find user"} =
+               build_conn()
+               |> get("/api/v1/accounts/-1")
+               |> json_response_and_validate_schema(404)
     end
 
     test "works by nickname" do
       user = insert(:user)
 
-      conn =
-        build_conn()
-        |> get("/api/v1/accounts/#{user.nickname}")
-
-      assert %{"id" => id} = json_response(conn, 200)
-      assert id == user.id
+      assert %{"id" => user_id} =
+               build_conn()
+               |> get("/api/v1/accounts/#{user.nickname}")
+               |> json_response_and_validate_schema(200)
     end
 
     test "works by nickname for remote users" do
       Config.put([:instance, :limit_to_local_content], false)
-      user = insert(:user, nickname: "user@example.com", local: false)
 
-      conn =
-        build_conn()
-        |> get("/api/v1/accounts/#{user.nickname}")
+      user = insert(:user, nickname: "user@example.com", local: false)
 
-      assert %{"id" => id} = json_response(conn, 200)
-      assert id == user.id
+      assert %{"id" => user_id} =
+               build_conn()
+               |> get("/api/v1/accounts/#{user.nickname}")
+               |> json_response_and_validate_schema(200)
     end
 
     test "respects limit_to_local_content == :all for remote user nicknames" do
@@ -63,11 +57,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
       user = insert(:user, nickname: "user@example.com", local: false)
 
-      conn =
-        build_conn()
-        |> get("/api/v1/accounts/#{user.nickname}")
-
-      assert json_response(conn, 404)
+      assert build_conn()
+             |> get("/api/v1/accounts/#{user.nickname}")
+             |> json_response_and_validate_schema(404)
     end
 
     test "respects limit_to_local_content == :unauthenticated for remote user nicknames" do
@@ -80,7 +72,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
         build_conn()
         |> get("/api/v1/accounts/#{user.nickname}")
 
-      assert json_response(conn, 404)
+      assert json_response_and_validate_schema(conn, 404)
 
       conn =
         build_conn()
@@ -88,7 +80,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
         |> assign(:token, insert(:oauth_token, user: reading_user, scopes: ["read:accounts"]))
         |> get("/api/v1/accounts/#{user.nickname}")
 
-      assert %{"id" => id} = json_response(conn, 200)
+      assert %{"id" => id} = json_response_and_validate_schema(conn, 200)
       assert id == user.id
     end
 
@@ -99,21 +91,21 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       user_one = insert(:user, %{id: 1212})
       user_two = insert(:user, %{nickname: "#{user_one.id}garbage"})
 
-      resp_one =
+      acc_one =
         conn
         |> get("/api/v1/accounts/#{user_one.id}")
+        |> json_response_and_validate_schema(:ok)
 
-      resp_two =
+      acc_two =
         conn
         |> get("/api/v1/accounts/#{user_two.nickname}")
+        |> json_response_and_validate_schema(:ok)
 
-      resp_three =
+      acc_three =
         conn
         |> get("/api/v1/accounts/#{user_two.id}")
+        |> json_response_and_validate_schema(:ok)
 
-      acc_one = json_response(resp_one, 200)
-      acc_two = json_response(resp_two, 200)
-      acc_three = json_response(resp_three, 200)
       refute acc_one == acc_two
       assert acc_two == acc_three
     end
@@ -121,23 +113,19 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
     test "returns 404 when user is invisible", %{conn: conn} do
       user = insert(:user, %{invisible: true})
 
-      resp =
-        conn
-        |> get("/api/v1/accounts/#{user.nickname}")
-        |> json_response(404)
-
-      assert %{"error" => "Can't find user"} = resp
+      assert %{"error" => "Can't find user"} =
+               conn
+               |> get("/api/v1/accounts/#{user.nickname}")
+               |> json_response_and_validate_schema(404)
     end
 
     test "returns 404 for internal.fetch actor", %{conn: conn} do
       %User{nickname: "internal.fetch"} = InternalFetchActor.get_actor()
 
-      resp =
-        conn
-        |> get("/api/v1/accounts/internal.fetch")
-        |> json_response(404)
-
-      assert %{"error" => "Can't find user"} = resp
+      assert %{"error" => "Can't find user"} =
+               conn
+               |> get("/api/v1/accounts/internal.fetch")
+               |> json_response_and_validate_schema(404)
     end
   end
 
@@ -155,27 +143,25 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
     setup do: clear_config([:restrict_unauthenticated, :profiles, :remote], true)
 
     test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
-      res_conn = get(conn, "/api/v1/accounts/#{local.id}")
-
-      assert json_response(res_conn, :not_found) == %{
-               "error" => "Can't find user"
-             }
-
-      res_conn = get(conn, "/api/v1/accounts/#{remote.id}")
-
-      assert json_response(res_conn, :not_found) == %{
-               "error" => "Can't find user"
-             }
+      assert %{"error" => "Can't find user"} ==
+               conn
+               |> get("/api/v1/accounts/#{local.id}")
+               |> json_response_and_validate_schema(:not_found)
+
+      assert %{"error" => "Can't find user"} ==
+               conn
+               |> get("/api/v1/accounts/#{remote.id}")
+               |> json_response_and_validate_schema(:not_found)
     end
 
     test "if user is authenticated", %{local: local, remote: remote} do
       %{conn: conn} = oauth_access(["read"])
 
       res_conn = get(conn, "/api/v1/accounts/#{local.id}")
-      assert %{"id" => _} = json_response(res_conn, 200)
+      assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
 
       res_conn = get(conn, "/api/v1/accounts/#{remote.id}")
-      assert %{"id" => _} = json_response(res_conn, 200)
+      assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
     end
   end
 
@@ -187,22 +173,22 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
     test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
       res_conn = get(conn, "/api/v1/accounts/#{local.id}")
 
-      assert json_response(res_conn, :not_found) == %{
+      assert json_response_and_validate_schema(res_conn, :not_found) == %{
                "error" => "Can't find user"
              }
 
       res_conn = get(conn, "/api/v1/accounts/#{remote.id}")
-      assert %{"id" => _} = json_response(res_conn, 200)
+      assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
     end
 
     test "if user is authenticated", %{local: local, remote: remote} do
       %{conn: conn} = oauth_access(["read"])
 
       res_conn = get(conn, "/api/v1/accounts/#{local.id}")
-      assert %{"id" => _} = json_response(res_conn, 200)
+      assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
 
       res_conn = get(conn, "/api/v1/accounts/#{remote.id}")
-      assert %{"id" => _} = json_response(res_conn, 200)
+      assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
     end
   end
 
@@ -213,11 +199,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
     test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
       res_conn = get(conn, "/api/v1/accounts/#{local.id}")
-      assert %{"id" => _} = json_response(res_conn, 200)
+      assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
 
       res_conn = get(conn, "/api/v1/accounts/#{remote.id}")
 
-      assert json_response(res_conn, :not_found) == %{
+      assert json_response_and_validate_schema(res_conn, :not_found) == %{
                "error" => "Can't find user"
              }
     end
@@ -226,10 +212,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       %{conn: conn} = oauth_access(["read"])
 
       res_conn = get(conn, "/api/v1/accounts/#{local.id}")
-      assert %{"id" => _} = json_response(res_conn, 200)
+      assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
 
       res_conn = get(conn, "/api/v1/accounts/#{remote.id}")
-      assert %{"id" => _} = json_response(res_conn, 200)
+      assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
     end
   end
 
@@ -245,27 +231,37 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       {:ok, activity} = CommonAPI.post(user_two, %{"status" => "User one sux0rz"})
       {:ok, repeat, _} = CommonAPI.repeat(activity.id, user_three)
 
-      resp = get(conn, "/api/v1/accounts/#{user_two.id}/statuses")
+      assert resp =
+               conn
+               |> get("/api/v1/accounts/#{user_two.id}/statuses")
+               |> json_response_and_validate_schema(200)
 
-      assert [%{"id" => id}] = json_response(resp, 200)
+      assert [%{"id" => id}] = resp
       assert id == activity.id
 
       # Even a blocked user will deliver the full user timeline, there would be
       #   no point in looking at a blocked users timeline otherwise
-      resp = get(conn, "/api/v1/accounts/#{user_two.id}/statuses")
+      assert resp =
+               conn
+               |> get("/api/v1/accounts/#{user_two.id}/statuses")
+               |> json_response_and_validate_schema(200)
 
-      assert [%{"id" => id}] = json_response(resp, 200)
+      assert [%{"id" => id}] = resp
       assert id == activity.id
 
       # Third user's timeline includes the repeat when viewed by unauthenticated user
-      resp = get(build_conn(), "/api/v1/accounts/#{user_three.id}/statuses")
-      assert [%{"id" => id}] = json_response(resp, 200)
+      resp =
+        build_conn()
+        |> get("/api/v1/accounts/#{user_three.id}/statuses")
+        |> json_response_and_validate_schema(200)
+
+      assert [%{"id" => id}] = resp
       assert id == repeat.id
 
       # When viewing a third user's timeline, the blocked users' statuses will NOT be shown
       resp = get(conn, "/api/v1/accounts/#{user_three.id}/statuses")
 
-      assert [] = json_response(resp, 200)
+      assert [] == json_response_and_validate_schema(resp, 200)
     end
 
     test "gets users statuses", %{conn: conn} do
@@ -286,9 +282,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       {:ok, private_activity} =
         CommonAPI.post(user_one, %{"status" => "private", "visibility" => "private"})
 
-      resp = get(conn, "/api/v1/accounts/#{user_one.id}/statuses")
+      # TODO!!!
+      resp =
+        conn
+        |> get("/api/v1/accounts/#{user_one.id}/statuses")
+        |> json_response_and_validate_schema(200)
 
-      assert [%{"id" => id}] = json_response(resp, 200)
+      assert [%{"id" => id}] = resp
       assert id == to_string(activity.id)
 
       resp =
@@ -296,8 +296,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
         |> assign(:user, user_two)
         |> assign(:token, insert(:oauth_token, user: user_two, scopes: ["read:statuses"]))
         |> get("/api/v1/accounts/#{user_one.id}/statuses")
+        |> json_response_and_validate_schema(200)
 
-      assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200)
+      assert [%{"id" => id_one}, %{"id" => id_two}] = resp
       assert id_one == to_string(direct_activity.id)
       assert id_two == to_string(activity.id)
 
@@ -306,8 +307,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
         |> assign(:user, user_three)
         |> assign(:token, insert(:oauth_token, user: user_three, scopes: ["read:statuses"]))
         |> get("/api/v1/accounts/#{user_one.id}/statuses")
+        |> json_response_and_validate_schema(200)
 
-      assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200)
+      assert [%{"id" => id_one}, %{"id" => id_two}] = resp
       assert id_one == to_string(private_activity.id)
       assert id_two == to_string(activity.id)
     end
@@ -318,7 +320,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
       conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?pinned=true")
 
-      assert json_response(conn, 200) == []
+      assert json_response_and_validate_schema(conn, 200) == []
     end
 
     test "gets an users media", %{conn: conn} do
@@ -333,56 +335,48 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
       {:ok, %{id: media_id}} = ActivityPub.upload(file, actor: user.ap_id)
 
-      {:ok, image_post} = CommonAPI.post(user, %{"status" => "cofe", "media_ids" => [media_id]})
+      {:ok, %{id: image_post_id}} =
+        CommonAPI.post(user, %{"status" => "cofe", "media_ids" => [media_id]})
 
-      conn = get(conn, "/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "true"})
+      conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?only_media=true")
 
-      assert [%{"id" => id}] = json_response(conn, 200)
-      assert id == to_string(image_post.id)
+      assert [%{"id" => ^image_post_id}] = json_response_and_validate_schema(conn, 200)
 
-      conn = get(build_conn(), "/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "1"})
+      conn = get(build_conn(), "/api/v1/accounts/#{user.id}/statuses?only_media=1")
 
-      assert [%{"id" => id}] = json_response(conn, 200)
-      assert id == to_string(image_post.id)
+      assert [%{"id" => ^image_post_id}] = json_response_and_validate_schema(conn, 200)
     end
 
     test "gets a user's statuses without reblogs", %{user: user, conn: conn} do
-      {:ok, post} = CommonAPI.post(user, %{"status" => "HI!!!"})
-      {:ok, _, _} = CommonAPI.repeat(post.id, user)
-
-      conn = get(conn, "/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "true"})
+      {:ok, %{id: post_id}} = CommonAPI.post(user, %{"status" => "HI!!!"})
+      {:ok, _, _} = CommonAPI.repeat(post_id, user)
 
-      assert [%{"id" => id}] = json_response(conn, 200)
-      assert id == to_string(post.id)
+      conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?exclude_reblogs=true")
+      assert [%{"id" => ^post_id}] = json_response_and_validate_schema(conn, 200)
 
-      conn = get(conn, "/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "1"})
-
-      assert [%{"id" => id}] = json_response(conn, 200)
-      assert id == to_string(post.id)
+      conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?exclude_reblogs=1")
+      assert [%{"id" => ^post_id}] = json_response_and_validate_schema(conn, 200)
     end
 
     test "filters user's statuses by a hashtag", %{user: user, conn: conn} do
-      {:ok, post} = CommonAPI.post(user, %{"status" => "#hashtag"})
+      {:ok, %{id: post_id}} = CommonAPI.post(user, %{"status" => "#hashtag"})
       {:ok, _post} = CommonAPI.post(user, %{"status" => "hashtag"})
 
-      conn = get(conn, "/api/v1/accounts/#{user.id}/statuses", %{"tagged" => "hashtag"})
-
-      assert [%{"id" => id}] = json_response(conn, 200)
-      assert id == to_string(post.id)
+      conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?tagged=hashtag")
+      assert [%{"id" => ^post_id}] = json_response_and_validate_schema(conn, 200)
     end
 
     test "the user views their own timelines and excludes direct messages", %{
       user: user,
       conn: conn
     } do
-      {:ok, public_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "public"})
-      {:ok, _direct_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"})
+      {:ok, %{id: public_activity_id}} =
+        CommonAPI.post(user, %{"status" => ".", "visibility" => "public"})
 
-      conn =
-        get(conn, "/api/v1/accounts/#{user.id}/statuses", %{"exclude_visibilities" => ["direct"]})
+      {:ok, _direct_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"})
 
-      assert [%{"id" => id}] = json_response(conn, 200)
-      assert id == to_string(public_activity.id)
+      conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?exclude_visibilities[]=direct")
+      assert [%{"id" => ^public_activity_id}] = json_response_and_validate_schema(conn, 200)
     end
   end
 
@@ -402,27 +396,25 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
     setup do: clear_config([:restrict_unauthenticated, :profiles, :remote], true)
 
     test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
-      res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses")
-
-      assert json_response(res_conn, :not_found) == %{
-               "error" => "Can't find user"
-             }
-
-      res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses")
-
-      assert json_response(res_conn, :not_found) == %{
-               "error" => "Can't find user"
-             }
+      assert %{"error" => "Can't find user"} ==
+               conn
+               |> get("/api/v1/accounts/#{local.id}/statuses")
+               |> json_response_and_validate_schema(:not_found)
+
+      assert %{"error" => "Can't find user"} ==
+               conn
+               |> get("/api/v1/accounts/#{remote.id}/statuses")
+               |> json_response_and_validate_schema(:not_found)
     end
 
     test "if user is authenticated", %{local: local, remote: remote} do
       %{conn: conn} = oauth_access(["read"])
 
       res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses")
-      assert length(json_response(res_conn, 200)) == 1
+      assert length(json_response_and_validate_schema(res_conn, 200)) == 1
 
       res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses")
-      assert length(json_response(res_conn, 200)) == 1
+      assert length(json_response_and_validate_schema(res_conn, 200)) == 1
     end
   end
 
@@ -433,24 +425,23 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
     setup do: clear_config([:restrict_unauthenticated, :profiles, :local], true)
 
     test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
-      res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses")
-
-      assert json_response(res_conn, :not_found) == %{
-               "error" => "Can't find user"
-             }
+      assert %{"error" => "Can't find user"} ==
+               conn
+               |> get("/api/v1/accounts/#{local.id}/statuses")
+               |> json_response_and_validate_schema(:not_found)
 
       res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses")
-      assert length(json_response(res_conn, 200)) == 1
+      assert length(json_response_and_validate_schema(res_conn, 200)) == 1
     end
 
     test "if user is authenticated", %{local: local, remote: remote} do
       %{conn: conn} = oauth_access(["read"])
 
       res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses")
-      assert length(json_response(res_conn, 200)) == 1
+      assert length(json_response_and_validate_schema(res_conn, 200)) == 1
 
       res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses")
-      assert length(json_response(res_conn, 200)) == 1
+      assert length(json_response_and_validate_schema(res_conn, 200)) == 1
     end
   end
 
@@ -462,23 +453,22 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
     test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
       res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses")
-      assert length(json_response(res_conn, 200)) == 1
-
-      res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses")
+      assert length(json_response_and_validate_schema(res_conn, 200)) == 1
 
-      assert json_response(res_conn, :not_found) == %{
-               "error" => "Can't find user"
-             }
+      assert %{"error" => "Can't find user"} ==
+               conn
+               |> get("/api/v1/accounts/#{remote.id}/statuses")
+               |> json_response_and_validate_schema(:not_found)
     end
 
     test "if user is authenticated", %{local: local, remote: remote} do
       %{conn: conn} = oauth_access(["read"])
 
       res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses")
-      assert length(json_response(res_conn, 200)) == 1
+      assert length(json_response_and_validate_schema(res_conn, 200)) == 1
 
       res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses")
-      assert length(json_response(res_conn, 200)) == 1
+      assert length(json_response_and_validate_schema(res_conn, 200)) == 1
     end
   end
 
@@ -487,12 +477,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
     test "getting followers", %{user: user, conn: conn} do
       other_user = insert(:user)
-      {:ok, user} = User.follow(user, other_user)
+      {:ok, %{id: user_id}} = User.follow(user, other_user)
 
       conn = get(conn, "/api/v1/accounts/#{other_user.id}/followers")
 
-      assert [%{"id" => id}] = json_response(conn, 200)
-      assert id == to_string(user.id)
+      assert [%{"id" => ^user_id}] = json_response_and_validate_schema(conn, 200)
     end
 
     test "getting followers, hide_followers", %{user: user, conn: conn} do
@@ -501,7 +490,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
       conn = get(conn, "/api/v1/accounts/#{other_user.id}/followers")
 
-      assert [] == json_response(conn, 200)
+      assert [] == json_response_and_validate_schema(conn, 200)
     end
 
     test "getting followers, hide_followers, same user requesting" do
@@ -515,37 +504,31 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
         |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"]))
         |> get("/api/v1/accounts/#{other_user.id}/followers")
 
-      refute [] == json_response(conn, 200)
+      refute [] == json_response_and_validate_schema(conn, 200)
     end
 
     test "getting followers, pagination", %{user: user, conn: conn} do
-      follower1 = insert(:user)
-      follower2 = insert(:user)
-      follower3 = insert(:user)
-      {:ok, _} = User.follow(follower1, user)
-      {:ok, _} = User.follow(follower2, user)
-      {:ok, _} = User.follow(follower3, user)
-
-      res_conn = get(conn, "/api/v1/accounts/#{user.id}/followers?since_id=#{follower1.id}")
+      {:ok, %User{id: follower1_id}} = :user |> insert() |> User.follow(user)
+      {:ok, %User{id: follower2_id}} = :user |> insert() |> User.follow(user)
+      {:ok, %User{id: follower3_id}} = :user |> insert() |> User.follow(user)
 
-      assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200)
-      assert id3 == follower3.id
-      assert id2 == follower2.id
+      assert [%{"id" => ^follower3_id}, %{"id" => ^follower2_id}] =
+               conn
+               |> get("/api/v1/accounts/#{user.id}/followers?since_id=#{follower1_id}")
+               |> json_response_and_validate_schema(200)
 
-      res_conn = get(conn, "/api/v1/accounts/#{user.id}/followers?max_id=#{follower3.id}")
+      assert [%{"id" => ^follower2_id}, %{"id" => ^follower1_id}] =
+               conn
+               |> get("/api/v1/accounts/#{user.id}/followers?max_id=#{follower3_id}")
+               |> json_response_and_validate_schema(200)
 
-      assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200)
-      assert id2 == follower2.id
-      assert id1 == follower1.id
+      res_conn = get(conn, "/api/v1/accounts/#{user.id}/followers?limit=1&max_id=#{follower3_id}")
 
-      res_conn = get(conn, "/api/v1/accounts/#{user.id}/followers?limit=1&max_id=#{follower3.id}")
-
-      assert [%{"id" => id2}] = json_response(res_conn, 200)
-      assert id2 == follower2.id
+      assert [%{"id" => ^follower2_id}] = json_response_and_validate_schema(res_conn, 200)
 
       assert [link_header] = get_resp_header(res_conn, "link")
-      assert link_header =~ ~r/min_id=#{follower2.id}/
-      assert link_header =~ ~r/max_id=#{follower2.id}/
+      assert link_header =~ ~r/min_id=#{follower2_id}/
+      assert link_header =~ ~r/max_id=#{follower2_id}/
     end
   end
 
@@ -558,7 +541,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
       conn = get(conn, "/api/v1/accounts/#{user.id}/following")
 
-      assert [%{"id" => id}] = json_response(conn, 200)
+      assert [%{"id" => id}] = json_response_and_validate_schema(conn, 200)
       assert id == to_string(other_user.id)
     end
 
@@ -573,7 +556,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
         |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"]))
         |> get("/api/v1/accounts/#{user.id}/following")
 
-      assert [] == json_response(conn, 200)
+      assert [] == json_response_and_validate_schema(conn, 200)
     end
 
     test "getting following, hide_follows, same user requesting" do
@@ -587,7 +570,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
         |> assign(:token, insert(:oauth_token, user: user, scopes: ["read:accounts"]))
         |> get("/api/v1/accounts/#{user.id}/following")
 
-      refute [] == json_response(conn, 200)
+      refute [] == json_response_and_validate_schema(conn, 200)
     end
 
     test "getting following, pagination", %{user: user, conn: conn} do
@@ -600,20 +583,20 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
       res_conn = get(conn, "/api/v1/accounts/#{user.id}/following?since_id=#{following1.id}")
 
-      assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200)
+      assert [%{"id" => id3}, %{"id" => id2}] = json_response_and_validate_schema(res_conn, 200)
       assert id3 == following3.id
       assert id2 == following2.id
 
       res_conn = get(conn, "/api/v1/accounts/#{user.id}/following?max_id=#{following3.id}")
 
-      assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200)
+      assert [%{"id" => id2}, %{"id" => id1}] = json_response_and_validate_schema(res_conn, 200)
       assert id2 == following2.id
       assert id1 == following1.id
 
       res_conn =
         get(conn, "/api/v1/accounts/#{user.id}/following?limit=1&max_id=#{following3.id}")
 
-      assert [%{"id" => id2}] = json_response(res_conn, 200)
+      assert [%{"id" => id2}] = json_response_and_validate_schema(res_conn, 200)
       assert id2 == following2.id
 
       assert [link_header] = get_resp_header(res_conn, "link")
@@ -626,30 +609,37 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
     setup do: oauth_access(["follow"])
 
     test "following / unfollowing a user", %{conn: conn} do
-      other_user = insert(:user)
-
-      ret_conn = post(conn, "/api/v1/accounts/#{other_user.id}/follow")
-
-      assert %{"id" => _id, "following" => true} = json_response(ret_conn, 200)
-
-      ret_conn = post(conn, "/api/v1/accounts/#{other_user.id}/unfollow")
-
-      assert %{"id" => _id, "following" => false} = json_response(ret_conn, 200)
-
-      conn = post(conn, "/api/v1/follows", %{"uri" => other_user.nickname})
-
-      assert %{"id" => id} = json_response(conn, 200)
-      assert id == to_string(other_user.id)
+      %{id: other_user_id, nickname: other_user_nickname} = insert(:user)
+
+      assert %{"id" => _id, "following" => true} =
+               conn
+               |> post("/api/v1/accounts/#{other_user_id}/follow")
+               |> json_response_and_validate_schema(200)
+
+      assert %{"id" => _id, "following" => false} =
+               conn
+               |> post("/api/v1/accounts/#{other_user_id}/unfollow")
+               |> json_response_and_validate_schema(200)
+
+      assert %{"id" => ^other_user_id} =
+               conn
+               |> put_req_header("content-type", "application/json")
+               |> post("/api/v1/follows", %{"uri" => other_user_nickname})
+               |> json_response_and_validate_schema(200)
     end
 
     test "cancelling follow request", %{conn: conn} do
       %{id: other_user_id} = insert(:user, %{locked: true})
 
       assert %{"id" => ^other_user_id, "following" => false, "requested" => true} =
-               conn |> post("/api/v1/accounts/#{other_user_id}/follow") |> json_response(:ok)
+               conn
+               |> post("/api/v1/accounts/#{other_user_id}/follow")
+               |> json_response_and_validate_schema(:ok)
 
       assert %{"id" => ^other_user_id, "following" => false, "requested" => false} =
-               conn |> post("/api/v1/accounts/#{other_user_id}/unfollow") |> json_response(:ok)
+               conn
+               |> post("/api/v1/accounts/#{other_user_id}/unfollow")
+               |> json_response_and_validate_schema(:ok)
     end
 
     test "following without reblogs" do
@@ -659,51 +649,65 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
       ret_conn = post(conn, "/api/v1/accounts/#{followed.id}/follow?reblogs=false")
 
-      assert %{"showing_reblogs" => false} = json_response(ret_conn, 200)
+      assert %{"showing_reblogs" => false} = json_response_and_validate_schema(ret_conn, 200)
 
       {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hey"})
-      {:ok, reblog, _} = CommonAPI.repeat(activity.id, followed)
-
-      ret_conn = get(conn, "/api/v1/timelines/home")
-
-      assert [] == json_response(ret_conn, 200)
-
-      ret_conn = post(conn, "/api/v1/accounts/#{followed.id}/follow?reblogs=true")
-
-      assert %{"showing_reblogs" => true} = json_response(ret_conn, 200)
-
-      conn = get(conn, "/api/v1/timelines/home")
-
-      expected_activity_id = reblog.id
-      assert [%{"id" => ^expected_activity_id}] = json_response(conn, 200)
+      {:ok, %{id: reblog_id}, _} = CommonAPI.repeat(activity.id, followed)
+
+      assert [] ==
+               conn
+               |> get("/api/v1/timelines/home")
+               |> json_response(200)
+
+      assert %{"showing_reblogs" => true} =
+               conn
+               |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=true")
+               |> json_response_and_validate_schema(200)
+
+      assert [%{"id" => ^reblog_id}] =
+               conn
+               |> get("/api/v1/timelines/home")
+               |> json_response(200)
     end
 
     test "following / unfollowing errors", %{user: user, conn: conn} do
       # self follow
       conn_res = post(conn, "/api/v1/accounts/#{user.id}/follow")
-      assert %{"error" => "Can not follow yourself"} = json_response(conn_res, 400)
+
+      assert %{"error" => "Can not follow yourself"} =
+               json_response_and_validate_schema(conn_res, 400)
 
       # self unfollow
       user = User.get_cached_by_id(user.id)
       conn_res = post(conn, "/api/v1/accounts/#{user.id}/unfollow")
-      assert %{"error" => "Can not unfollow yourself"} = json_response(conn_res, 400)
+
+      assert %{"error" => "Can not unfollow yourself"} =
+               json_response_and_validate_schema(conn_res, 400)
 
       # self follow via uri
       user = User.get_cached_by_id(user.id)
-      conn_res = post(conn, "/api/v1/follows", %{"uri" => user.nickname})
-      assert %{"error" => "Can not follow yourself"} = json_response(conn_res, 400)
+
+      assert %{"error" => "Can not follow yourself"} =
+               conn
+               |> put_req_header("content-type", "multipart/form-data")
+               |> post("/api/v1/follows", %{"uri" => user.nickname})
+               |> json_response_and_validate_schema(400)
 
       # follow non existing user
       conn_res = post(conn, "/api/v1/accounts/doesntexist/follow")
-      assert %{"error" => "Record not found"} = json_response(conn_res, 404)
+      assert %{"error" => "Record not found"} = json_response_and_validate_schema(conn_res, 404)
 
       # follow non existing user via uri
-      conn_res = post(conn, "/api/v1/follows", %{"uri" => "doesntexist"})
-      assert %{"error" => "Record not found"} = json_response(conn_res, 404)
+      conn_res =
+        conn
+        |> put_req_header("content-type", "multipart/form-data")
+        |> post("/api/v1/follows", %{"uri" => "doesntexist"})
+
+      assert %{"error" => "Record not found"} = json_response_and_validate_schema(conn_res, 404)
 
       # unfollow non existing user
       conn_res = post(conn, "/api/v1/accounts/doesntexist/unfollow")
-      assert %{"error" => "Record not found"} = json_response(conn_res, 404)
+      assert %{"error" => "Record not found"} = json_response_and_validate_schema(conn_res, 404)
     end
   end
 
@@ -713,32 +717,33 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
     test "with notifications", %{conn: conn} do
       other_user = insert(:user)
 
-      ret_conn = post(conn, "/api/v1/accounts/#{other_user.id}/mute")
-
-      response = json_response(ret_conn, 200)
-
-      assert %{"id" => _id, "muting" => true, "muting_notifications" => true} = response
+      assert %{"id" => _id, "muting" => true, "muting_notifications" => true} =
+               conn
+               |> put_req_header("content-type", "application/json")
+               |> post("/api/v1/accounts/#{other_user.id}/mute")
+               |> json_response_and_validate_schema(200)
 
       conn = post(conn, "/api/v1/accounts/#{other_user.id}/unmute")
 
-      response = json_response(conn, 200)
-      assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response
+      assert %{"id" => _id, "muting" => false, "muting_notifications" => false} =
+               json_response_and_validate_schema(conn, 200)
     end
 
     test "without notifications", %{conn: conn} do
       other_user = insert(:user)
 
       ret_conn =
-        post(conn, "/api/v1/accounts/#{other_user.id}/mute", %{"notifications" => "false"})
-
-      response = json_response(ret_conn, 200)
+        conn
+        |> put_req_header("content-type", "multipart/form-data")
+        |> post("/api/v1/accounts/#{other_user.id}/mute", %{"notifications" => "false"})
 
-      assert %{"id" => _id, "muting" => true, "muting_notifications" => false} = response
+      assert %{"id" => _id, "muting" => true, "muting_notifications" => false} =
+               json_response_and_validate_schema(ret_conn, 200)
 
       conn = post(conn, "/api/v1/accounts/#{other_user.id}/unmute")
 
-      response = json_response(conn, 200)
-      assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response
+      assert %{"id" => _id, "muting" => false, "muting_notifications" => false} =
+               json_response_and_validate_schema(conn, 200)
     end
   end
 
@@ -751,17 +756,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       [conn: conn, user: user, activity: activity]
     end
 
-    test "returns pinned statuses", %{conn: conn, user: user, activity: activity} do
-      {:ok, _} = CommonAPI.pin(activity.id, user)
+    test "returns pinned statuses", %{conn: conn, user: user, activity: %{id: activity_id}} do
+      {:ok, _} = CommonAPI.pin(activity_id, user)
 
-      result =
-        conn
-        |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
-        |> json_response(200)
-
-      id_str = to_string(activity.id)
-
-      assert [%{"id" => ^id_str, "pinned" => true}] = result
+      assert [%{"id" => ^activity_id, "pinned" => true}] =
+               conn
+               |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
+               |> json_response_and_validate_schema(200)
     end
   end
 
@@ -771,11 +772,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
     ret_conn = post(conn, "/api/v1/accounts/#{other_user.id}/block")
 
-    assert %{"id" => _id, "blocking" => true} = json_response(ret_conn, 200)
+    assert %{"id" => _id, "blocking" => true} = json_response_and_validate_schema(ret_conn, 200)
 
     conn = post(conn, "/api/v1/accounts/#{other_user.id}/unblock")
 
-    assert %{"id" => _id, "blocking" => false} = json_response(conn, 200)
+    assert %{"id" => _id, "blocking" => false} = json_response_and_validate_schema(conn, 200)
   end
 
   describe "create account by app" do
@@ -802,15 +803,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
           scopes: "read, write, follow"
         })
 
-      %{
-        "client_id" => client_id,
-        "client_secret" => client_secret,
-        "id" => _,
-        "name" => "client_name",
-        "redirect_uri" => "urn:ietf:wg:oauth:2.0:oob",
-        "vapid_key" => _,
-        "website" => nil
-      } = json_response(conn, 200)
+      assert %{
+               "client_id" => client_id,
+               "client_secret" => client_secret,
+               "id" => _,
+               "name" => "client_name",
+               "redirect_uri" => "urn:ietf:wg:oauth:2.0:oob",
+               "vapid_key" => _,
+               "website" => nil
+             } = json_response_and_validate_schema(conn, 200)
 
       conn =
         post(conn, "/oauth/token", %{
@@ -830,6 +831,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
       conn =
         build_conn()
+        |> put_req_header("content-type", "multipart/form-data")
         |> put_req_header("authorization", "Bearer " <> token)
         |> post("/api/v1/accounts", %{
           username: "lain",
@@ -844,7 +846,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
         "created_at" => _created_at,
         "scope" => _scope,
         "token_type" => "Bearer"
-      } = json_response(conn, 200)
+      } = json_response_and_validate_schema(conn, 200)
 
       token_from_db = Repo.get_by(Token, token: token)
       assert token_from_db
@@ -858,12 +860,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       _user = insert(:user, email: "lain@example.org")
       app_token = insert(:oauth_token, user: nil)
 
-      conn =
+      res =
         conn
         |> put_req_header("authorization", "Bearer " <> app_token.token)
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/v1/accounts", valid_params)
 
-      res = post(conn, "/api/v1/accounts", valid_params)
-      assert json_response(res, 400) == %{"error" => "{\"email\":[\"has already been taken\"]}"}
+      assert json_response_and_validate_schema(res, 400) == %{
+               "error" => "{\"email\":[\"has already been taken\"]}"
+             }
     end
 
     test "returns bad_request if missing required params", %{
@@ -872,10 +877,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
     } do
       app_token = insert(:oauth_token, user: nil)
 
-      conn = put_req_header(conn, "authorization", "Bearer " <> app_token.token)
+      conn =
+        conn
+        |> put_req_header("authorization", "Bearer " <> app_token.token)
+        |> put_req_header("content-type", "application/json")
 
       res = post(conn, "/api/v1/accounts", valid_params)
-      assert json_response(res, 200)
+      assert json_response_and_validate_schema(res, 200)
 
       [{127, 0, 0, 1}, {127, 0, 0, 2}, {127, 0, 0, 3}, {127, 0, 0, 4}]
       |> Stream.zip(Map.delete(valid_params, :email))
@@ -884,9 +892,18 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
           conn
           |> Map.put(:remote_ip, ip)
           |> post("/api/v1/accounts", Map.delete(valid_params, attr))
-          |> json_response(400)
-
-        assert res == %{"error" => "Missing parameters"}
+          |> json_response_and_validate_schema(400)
+
+        assert res == %{
+                 "error" => "Missing field: #{attr}.",
+                 "errors" => [
+                   %{
+                     "message" => "Missing field: #{attr}",
+                     "source" => %{"pointer" => "/#{attr}"},
+                     "title" => "Invalid value"
+                   }
+                 ]
+               }
       end)
     end
 
@@ -897,21 +914,27 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       Pleroma.Config.put([:instance, :account_activation_required], true)
 
       app_token = insert(:oauth_token, user: nil)
-      conn = put_req_header(conn, "authorization", "Bearer " <> app_token.token)
+
+      conn =
+        conn
+        |> put_req_header("authorization", "Bearer " <> app_token.token)
+        |> put_req_header("content-type", "application/json")
 
       res =
         conn
         |> Map.put(:remote_ip, {127, 0, 0, 5})
         |> post("/api/v1/accounts", Map.delete(valid_params, :email))
 
-      assert json_response(res, 400) == %{"error" => "Missing parameters"}
+      assert json_response_and_validate_schema(res, 400) == %{"error" => "Missing parameters"}
 
       res =
         conn
         |> Map.put(:remote_ip, {127, 0, 0, 6})
         |> post("/api/v1/accounts", Map.put(valid_params, :email, ""))
 
-      assert json_response(res, 400) == %{"error" => "{\"email\":[\"can't be blank\"]}"}
+      assert json_response_and_validate_schema(res, 400) == %{
+               "error" => "{\"email\":[\"can't be blank\"]}"
+             }
     end
 
     test "allow registration without an email", %{conn: conn, valid_params: valid_params} do
@@ -920,10 +943,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
       res =
         conn
+        |> put_req_header("content-type", "application/json")
         |> Map.put(:remote_ip, {127, 0, 0, 7})
         |> post("/api/v1/accounts", Map.delete(valid_params, :email))
 
-      assert json_response(res, 200)
+      assert json_response_and_validate_schema(res, 200)
     end
 
     test "allow registration with an empty email", %{conn: conn, valid_params: valid_params} do
@@ -932,17 +956,21 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
       res =
         conn
+        |> put_req_header("content-type", "application/json")
         |> Map.put(:remote_ip, {127, 0, 0, 8})
         |> post("/api/v1/accounts", Map.put(valid_params, :email, ""))
 
-      assert json_response(res, 200)
+      assert json_response_and_validate_schema(res, 200)
     end
 
     test "returns forbidden if token is invalid", %{conn: conn, valid_params: valid_params} do
-      conn = put_req_header(conn, "authorization", "Bearer " <> "invalid-token")
+      res =
+        conn
+        |> put_req_header("authorization", "Bearer " <> "invalid-token")
+        |> put_req_header("content-type", "multipart/form-data")
+        |> post("/api/v1/accounts", valid_params)
 
-      res = post(conn, "/api/v1/accounts", valid_params)
-      assert json_response(res, 403) == %{"error" => "Invalid credentials"}
+      assert json_response_and_validate_schema(res, 403) == %{"error" => "Invalid credentials"}
     end
 
     test "registration from trusted app" do
@@ -962,6 +990,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       response =
         build_conn()
         |> Plug.Conn.put_req_header("authorization", "Bearer " <> token)
+        |> put_req_header("content-type", "multipart/form-data")
         |> post("/api/v1/accounts", %{
           nickname: "nickanme",
           agreement: true,
@@ -971,7 +1000,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
           password: "some_password",
           confirm: "some_password"
         })
-        |> json_response(200)
+        |> json_response_and_validate_schema(200)
 
       assert %{
                "access_token" => access_token,
@@ -984,7 +1013,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
         build_conn()
         |> Plug.Conn.put_req_header("authorization", "Bearer " <> access_token)
         |> get("/api/v1/accounts/verify_credentials")
-        |> json_response(200)
+        |> json_response_and_validate_schema(200)
 
       assert %{
                "acct" => "Lain",
@@ -1023,10 +1052,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
         conn
         |> put_req_header("authorization", "Bearer " <> app_token.token)
         |> Map.put(:remote_ip, {15, 15, 15, 15})
+        |> put_req_header("content-type", "multipart/form-data")
 
       for i <- 1..2 do
         conn =
-          post(conn, "/api/v1/accounts", %{
+          conn
+          |> post("/api/v1/accounts", %{
             username: "#{i}lain",
             email: "#{i}lain@example.org",
             password: "PlzDontHackLain",
@@ -1038,7 +1069,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
           "created_at" => _created_at,
           "scope" => _scope,
           "token_type" => "Bearer"
-        } = json_response(conn, 200)
+        } = json_response_and_validate_schema(conn, 200)
 
         token_from_db = Repo.get_by(Token, token: token)
         assert token_from_db
@@ -1056,7 +1087,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
           agreement: true
         })
 
-      assert json_response(conn, :too_many_requests) == %{"error" => "Throttled"}
+      assert json_response_and_validate_schema(conn, :too_many_requests) == %{
+               "error" => "Throttled"
+             }
     end
   end
 
@@ -1064,15 +1097,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
     test "returns lists to which the account belongs" do
       %{user: user, conn: conn} = oauth_access(["read:lists"])
       other_user = insert(:user)
-      assert {:ok, %Pleroma.List{} = list} = Pleroma.List.create("Test List", user)
+      assert {:ok, %Pleroma.List{id: list_id} = list} = Pleroma.List.create("Test List", user)
       {:ok, %{following: _following}} = Pleroma.List.follow(list, other_user)
 
-      res =
-        conn
-        |> get("/api/v1/accounts/#{other_user.id}/lists")
-        |> json_response(200)
-
-      assert res == [%{"id" => to_string(list.id), "title" => "Test List"}]
+      assert [%{"id" => list_id, "title" => "Test List"}] =
+               conn
+               |> get("/api/v1/accounts/#{other_user.id}/lists")
+               |> json_response_and_validate_schema(200)
     end
   end
 
@@ -1081,7 +1112,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       %{user: user, conn: conn} = oauth_access(["read:accounts"])
       conn = get(conn, "/api/v1/accounts/verify_credentials")
 
-      response = json_response(conn, 200)
+      response = json_response_and_validate_schema(conn, 200)
 
       assert %{"id" => id, "source" => %{"privacy" => "public"}} = response
       assert response["pleroma"]["chat_token"]
@@ -1094,7 +1125,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
       conn = get(conn, "/api/v1/accounts/verify_credentials")
 
-      assert %{"id" => id, "source" => %{"privacy" => "unlisted"}} = json_response(conn, 200)
+      assert %{"id" => id, "source" => %{"privacy" => "unlisted"}} =
+               json_response_and_validate_schema(conn, 200)
+
       assert id == to_string(user.id)
     end
 
@@ -1104,7 +1137,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
       conn = get(conn, "/api/v1/accounts/verify_credentials")
 
-      assert %{"id" => id, "source" => %{"privacy" => "private"}} = json_response(conn, 200)
+      assert %{"id" => id, "source" => %{"privacy" => "private"}} =
+               json_response_and_validate_schema(conn, 200)
+
       assert id == to_string(user.id)
     end
   end
@@ -1113,20 +1148,24 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
     setup do: oauth_access(["read:follows"])
 
     test "returns the relationships for the current user", %{user: user, conn: conn} do
-      other_user = insert(:user)
+      %{id: other_user_id} = other_user = insert(:user)
       {:ok, _user} = User.follow(user, other_user)
 
-      conn = get(conn, "/api/v1/accounts/relationships", %{"id" => [other_user.id]})
-
-      assert [relationship] = json_response(conn, 200)
+      assert [%{"id" => ^other_user_id}] =
+               conn
+               |> get("/api/v1/accounts/relationships?id=#{other_user.id}")
+               |> json_response_and_validate_schema(200)
 
-      assert to_string(other_user.id) == relationship["id"]
+      assert [%{"id" => ^other_user_id}] =
+               conn
+               |> get("/api/v1/accounts/relationships?id[]=#{other_user.id}")
+               |> json_response_and_validate_schema(200)
     end
 
     test "returns an empty list on a bad request", %{conn: conn} do
       conn = get(conn, "/api/v1/accounts/relationships", %{})
 
-      assert [] = json_response(conn, 200)
+      assert [] = json_response_and_validate_schema(conn, 200)
     end
   end
 
@@ -1139,7 +1178,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
     conn = get(conn, "/api/v1/mutes")
 
     other_user_id = to_string(other_user.id)
-    assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
+    assert [%{"id" => ^other_user_id}] = json_response_and_validate_schema(conn, 200)
   end
 
   test "getting a list of blocks" do
@@ -1154,6 +1193,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       |> get("/api/v1/blocks")
 
     other_user_id = to_string(other_user.id)
-    assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
+    assert [%{"id" => ^other_user_id}] = json_response_and_validate_schema(conn, 200)
   end
 end
index e7b11d14e1461fa910f72ddd73c8b5359a4f5b21..a0b8b126c9536594ba307b875bfa41ff7477bb78 100644 (file)
@@ -27,7 +27,7 @@ defmodule Pleroma.Web.MastodonAPI.AppControllerTest do
       "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
     }
 
-    assert expected == json_response(conn, 200)
+    assert expected == json_response_and_validate_schema(conn, 200)
   end
 
   test "creates an oauth app", %{conn: conn} do
@@ -55,6 +55,6 @@ defmodule Pleroma.Web.MastodonAPI.AppControllerTest do
       "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
     }
 
-    assert expected == json_response(conn, 200)
+    assert expected == json_response_and_validate_schema(conn, 200)
   end
 end
index 0b2ffa470d57c59fcf264e9d0f9b60578d3b735a..ab0027f9027dc6f6bdce76c2d826741f2d51b11c 100644 (file)
@@ -4,16 +4,12 @@
 
 defmodule Pleroma.Web.MastodonAPI.CustomEmojiControllerTest do
   use Pleroma.Web.ConnCase, async: true
-  alias Pleroma.Web.ApiSpec
-  alias Pleroma.Web.ApiSpec.Schemas.CustomEmoji
-  alias Pleroma.Web.ApiSpec.Schemas.CustomEmojisResponse
-  import OpenApiSpex.TestAssertions
 
   test "with tags", %{conn: conn} do
     assert resp =
              conn
              |> get("/api/v1/custom_emojis")
-             |> json_response(200)
+             |> json_response_and_validate_schema(200)
 
     assert [emoji | _body] = resp
     assert Map.has_key?(emoji, "shortcode")
@@ -23,19 +19,5 @@ defmodule Pleroma.Web.MastodonAPI.CustomEmojiControllerTest do
     assert Map.has_key?(emoji, "category")
     assert Map.has_key?(emoji, "url")
     assert Map.has_key?(emoji, "visible_in_picker")
-    assert_schema(resp, "CustomEmojisResponse", ApiSpec.spec())
-    assert_schema(emoji, "CustomEmoji", ApiSpec.spec())
-  end
-
-  test "CustomEmoji example matches schema" do
-    api_spec = ApiSpec.spec()
-    schema = CustomEmoji.schema()
-    assert_schema(schema.example, "CustomEmoji", api_spec)
-  end
-
-  test "CustomEmojisResponse example matches schema" do
-    api_spec = ApiSpec.spec()
-    schema = CustomEmojisResponse.schema()
-    assert_schema(schema.example, "CustomEmojisResponse", api_spec)
   end
 end
index d66190c90040d6460d886e4ec9e3b6c79843ed56..01a24afcf2225a35589b65b1c7aff63dd49648a4 100644 (file)
@@ -6,11 +6,8 @@ defmodule Pleroma.Web.MastodonAPI.DomainBlockControllerTest do
   use Pleroma.Web.ConnCase
 
   alias Pleroma.User
-  alias Pleroma.Web.ApiSpec
-  alias Pleroma.Web.ApiSpec.Schemas.DomainBlocksResponse
 
   import Pleroma.Factory
-  import OpenApiSpex.TestAssertions
 
   test "blocking / unblocking a domain" do
     %{user: user, conn: conn} = oauth_access(["write:blocks"])
@@ -21,7 +18,7 @@ defmodule Pleroma.Web.MastodonAPI.DomainBlockControllerTest do
       |> put_req_header("content-type", "application/json")
       |> post("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
 
-    assert %{} = json_response(ret_conn, 200)
+    assert %{} == json_response_and_validate_schema(ret_conn, 200)
     user = User.get_cached_by_ap_id(user.ap_id)
     assert User.blocks?(user, other_user)
 
@@ -30,7 +27,7 @@ defmodule Pleroma.Web.MastodonAPI.DomainBlockControllerTest do
       |> put_req_header("content-type", "application/json")
       |> delete("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
 
-    assert %{} = json_response(ret_conn, 200)
+    assert %{} == json_response_and_validate_schema(ret_conn, 200)
     user = User.get_cached_by_ap_id(user.ap_id)
     refute User.blocks?(user, other_user)
   end
@@ -41,21 +38,10 @@ defmodule Pleroma.Web.MastodonAPI.DomainBlockControllerTest do
     {:ok, user} = User.block_domain(user, "bad.site")
     {:ok, user} = User.block_domain(user, "even.worse.site")
 
-    conn =
-      conn
-      |> assign(:user, user)
-      |> get("/api/v1/domain_blocks")
-
-    domain_blocks = json_response(conn, 200)
-
-    assert "bad.site" in domain_blocks
-    assert "even.worse.site" in domain_blocks
-    assert_schema(domain_blocks, "DomainBlocksResponse", ApiSpec.spec())
-  end
-
-  test "DomainBlocksResponse example matches schema" do
-    api_spec = ApiSpec.spec()
-    schema = DomainBlocksResponse.schema()
-    assert_schema(schema.example, "DomainBlocksResponse", api_spec)
+    assert ["even.worse.site", "bad.site"] ==
+             conn
+             |> assign(:user, user)
+             |> get("/api/v1/domain_blocks")
+             |> json_response_and_validate_schema(200)
   end
 end
index 162f7b1b2ccdb36d01ec87c35b24cec2ccbd39b5..85068edd00eddf9a0551073ed6273d1962604f6d 100644 (file)
@@ -302,6 +302,17 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
       assert [] == Repo.all(Activity)
     end
 
+    test "ignores nil values", %{conn: conn} do
+      conn =
+        post(conn, "/api/v1/statuses", %{
+          "status" => "not scheduled",
+          "scheduled_at" => nil
+        })
+
+      assert result = json_response(conn, 200)
+      assert Activity.get_by_id(result["id"])
+    end
+
     test "creates a scheduled activity with a media attachment", %{user: user, conn: conn} do
       scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond)
 
index 291ae54fc1639ddb53f6d268c2b98a7a7b13721a..1ac2f2c27aa4fb3e4ab8bc0b626eb3ef10808b3c 100644 (file)
@@ -9,6 +9,7 @@ defmodule Pleroma.Web.MongooseIMController do
   test "/user_exists", %{conn: conn} do
     _user = insert(:user, nickname: "lain")
     _remote_user = insert(:user, nickname: "alice", local: false)
+    _deactivated_user = insert(:user, nickname: "konata", deactivated: true)
 
     res =
       conn
@@ -30,11 +31,25 @@ defmodule Pleroma.Web.MongooseIMController do
       |> json_response(404)
 
     assert res == false
+
+    res =
+      conn
+      |> get(mongoose_im_path(conn, :user_exists), user: "konata")
+      |> json_response(404)
+
+    assert res == false
   end
 
   test "/check_password", %{conn: conn} do
     user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt("cool"))
 
+    _deactivated_user =
+      insert(:user,
+        nickname: "konata",
+        deactivated: true,
+        password_hash: Comeonin.Pbkdf2.hashpwsalt("cool")
+      )
+
     res =
       conn
       |> get(mongoose_im_path(conn, :check_password), user: user.nickname, pass: "cool")
@@ -49,6 +64,13 @@ defmodule Pleroma.Web.MongooseIMController do
 
     assert res == false
 
+    res =
+      conn
+      |> get(mongoose_im_path(conn, :check_password), user: "konata", pass: "cool")
+      |> json_response(404)
+
+    assert res == false
+
     res =
       conn
       |> get(mongoose_im_path(conn, :check_password), user: "nobody", pass: "cool")
index f6e13b66127030a0faf24f7a1d9a9619d3d242c3..7926a075789ba6b64168e5e5252048bc4bdd40ec 100644 (file)
@@ -18,11 +18,11 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
 
   test "it registers a new user and returns the user." do
     data = %{
-      "nickname" => "lain",
-      "email" => "lain@wired.jp",
-      "fullname" => "lain iwakura",
-      "password" => "bear",
-      "confirm" => "bear"
+      :nickname => "lain",
+      :email => "lain@wired.jp",
+      :fullname => "lain iwakura",
+      :password => "bear",
+      :confirm => "bear"
     }
 
     {:ok, user} = TwitterAPI.register_user(data)
@@ -35,12 +35,12 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
 
   test "it registers a new user with empty string in bio and returns the user." do
     data = %{
-      "nickname" => "lain",
-      "email" => "lain@wired.jp",
-      "fullname" => "lain iwakura",
-      "bio" => "",
-      "password" => "bear",
-      "confirm" => "bear"
+      :nickname => "lain",
+      :email => "lain@wired.jp",
+      :fullname => "lain iwakura",
+      :bio => "",
+      :password => "bear",
+      :confirm => "bear"
     }
 
     {:ok, user} = TwitterAPI.register_user(data)
@@ -60,12 +60,12 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
     end
 
     data = %{
-      "nickname" => "lain",
-      "email" => "lain@wired.jp",
-      "fullname" => "lain iwakura",
-      "bio" => "",
-      "password" => "bear",
-      "confirm" => "bear"
+      :nickname => "lain",
+      :email => "lain@wired.jp",
+      :fullname => "lain iwakura",
+      :bio => "",
+      :password => "bear",
+      :confirm => "bear"
     }
 
     {:ok, user} = TwitterAPI.register_user(data)
@@ -87,23 +87,23 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
 
   test "it registers a new user and parses mentions in the bio" do
     data1 = %{
-      "nickname" => "john",
-      "email" => "john@gmail.com",
-      "fullname" => "John Doe",
-      "bio" => "test",
-      "password" => "bear",
-      "confirm" => "bear"
+      :nickname => "john",
+      :email => "john@gmail.com",
+      :fullname => "John Doe",
+      :bio => "test",
+      :password => "bear",
+      :confirm => "bear"
     }
 
     {:ok, user1} = TwitterAPI.register_user(data1)
 
     data2 = %{
-      "nickname" => "lain",
-      "email" => "lain@wired.jp",
-      "fullname" => "lain iwakura",
-      "bio" => "@john test",
-      "password" => "bear",
-      "confirm" => "bear"
+      :nickname => "lain",
+      :email => "lain@wired.jp",
+      :fullname => "lain iwakura",
+      :bio => "@john test",
+      :password => "bear",
+      :confirm => "bear"
     }
 
     {:ok, user2} = TwitterAPI.register_user(data2)
@@ -123,13 +123,13 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
       {:ok, invite} = UserInviteToken.create_invite()
 
       data = %{
-        "nickname" => "vinny",
-        "email" => "pasta@pizza.vs",
-        "fullname" => "Vinny Vinesauce",
-        "bio" => "streamer",
-        "password" => "hiptofbees",
-        "confirm" => "hiptofbees",
-        "token" => invite.token
+        :nickname => "vinny",
+        :email => "pasta@pizza.vs",
+        :fullname => "Vinny Vinesauce",
+        :bio => "streamer",
+        :password => "hiptofbees",
+        :confirm => "hiptofbees",
+        :token => invite.token
       }
 
       {:ok, user} = TwitterAPI.register_user(data)
@@ -145,13 +145,13 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
 
     test "returns error on invalid token" do
       data = %{
-        "nickname" => "GrimReaper",
-        "email" => "death@reapers.afterlife",
-        "fullname" => "Reaper Grim",
-        "bio" => "Your time has come",
-        "password" => "scythe",
-        "confirm" => "scythe",
-        "token" => "DudeLetMeInImAFairy"
+        :nickname => "GrimReaper",
+        :email => "death@reapers.afterlife",
+        :fullname => "Reaper Grim",
+        :bio => "Your time has come",
+        :password => "scythe",
+        :confirm => "scythe",
+        :token => "DudeLetMeInImAFairy"
       }
 
       {:error, msg} = TwitterAPI.register_user(data)
@@ -165,13 +165,13 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
       UserInviteToken.update_invite!(invite, used: true)
 
       data = %{
-        "nickname" => "GrimReaper",
-        "email" => "death@reapers.afterlife",
-        "fullname" => "Reaper Grim",
-        "bio" => "Your time has come",
-        "password" => "scythe",
-        "confirm" => "scythe",
-        "token" => invite.token
+        :nickname => "GrimReaper",
+        :email => "death@reapers.afterlife",
+        :fullname => "Reaper Grim",
+        :bio => "Your time has come",
+        :password => "scythe",
+        :confirm => "scythe",
+        :token => invite.token
       }
 
       {:error, msg} = TwitterAPI.register_user(data)
@@ -186,16 +186,16 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
 
     setup do
       data = %{
-        "nickname" => "vinny",
-        "email" => "pasta@pizza.vs",
-        "fullname" => "Vinny Vinesauce",
-        "bio" => "streamer",
-        "password" => "hiptofbees",
-        "confirm" => "hiptofbees"
+        :nickname => "vinny",
+        :email => "pasta@pizza.vs",
+        :fullname => "Vinny Vinesauce",
+        :bio => "streamer",
+        :password => "hiptofbees",
+        :confirm => "hiptofbees"
       }
 
       check_fn = fn invite ->
-        data = Map.put(data, "token", invite.token)
+        data = Map.put(data, :token, invite.token)
         {:ok, user} = TwitterAPI.register_user(data)
         fetched_user = User.get_cached_by_nickname("vinny")
 
@@ -250,13 +250,13 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
       UserInviteToken.update_invite!(invite, uses: 99)
 
       data = %{
-        "nickname" => "vinny",
-        "email" => "pasta@pizza.vs",
-        "fullname" => "Vinny Vinesauce",
-        "bio" => "streamer",
-        "password" => "hiptofbees",
-        "confirm" => "hiptofbees",
-        "token" => invite.token
+        :nickname => "vinny",
+        :email => "pasta@pizza.vs",
+        :fullname => "Vinny Vinesauce",
+        :bio => "streamer",
+        :password => "hiptofbees",
+        :confirm => "hiptofbees",
+        :token => invite.token
       }
 
       {:ok, user} = TwitterAPI.register_user(data)
@@ -269,13 +269,13 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
                AccountView.render("show.json", %{user: fetched_user})
 
       data = %{
-        "nickname" => "GrimReaper",
-        "email" => "death@reapers.afterlife",
-        "fullname" => "Reaper Grim",
-        "bio" => "Your time has come",
-        "password" => "scythe",
-        "confirm" => "scythe",
-        "token" => invite.token
+        :nickname => "GrimReaper",
+        :email => "death@reapers.afterlife",
+        :fullname => "Reaper Grim",
+        :bio => "Your time has come",
+        :password => "scythe",
+        :confirm => "scythe",
+        :token => invite.token
       }
 
       {:error, msg} = TwitterAPI.register_user(data)
@@ -292,13 +292,13 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
       {:ok, invite} = UserInviteToken.create_invite(%{expires_at: Date.utc_today(), max_use: 100})
 
       data = %{
-        "nickname" => "vinny",
-        "email" => "pasta@pizza.vs",
-        "fullname" => "Vinny Vinesauce",
-        "bio" => "streamer",
-        "password" => "hiptofbees",
-        "confirm" => "hiptofbees",
-        "token" => invite.token
+        :nickname => "vinny",
+        :email => "pasta@pizza.vs",
+        :fullname => "Vinny Vinesauce",
+        :bio => "streamer",
+        :password => "hiptofbees",
+        :confirm => "hiptofbees",
+        :token => invite.token
       }
 
       {:ok, user} = TwitterAPI.register_user(data)
@@ -317,13 +317,13 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
       UserInviteToken.update_invite!(invite, uses: 99)
 
       data = %{
-        "nickname" => "vinny",
-        "email" => "pasta@pizza.vs",
-        "fullname" => "Vinny Vinesauce",
-        "bio" => "streamer",
-        "password" => "hiptofbees",
-        "confirm" => "hiptofbees",
-        "token" => invite.token
+        :nickname => "vinny",
+        :email => "pasta@pizza.vs",
+        :fullname => "Vinny Vinesauce",
+        :bio => "streamer",
+        :password => "hiptofbees",
+        :confirm => "hiptofbees",
+        :token => invite.token
       }
 
       {:ok, user} = TwitterAPI.register_user(data)
@@ -335,13 +335,13 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
                AccountView.render("show.json", %{user: fetched_user})
 
       data = %{
-        "nickname" => "GrimReaper",
-        "email" => "death@reapers.afterlife",
-        "fullname" => "Reaper Grim",
-        "bio" => "Your time has come",
-        "password" => "scythe",
-        "confirm" => "scythe",
-        "token" => invite.token
+        :nickname => "GrimReaper",
+        :email => "death@reapers.afterlife",
+        :fullname => "Reaper Grim",
+        :bio => "Your time has come",
+        :password => "scythe",
+        :confirm => "scythe",
+        :token => invite.token
       }
 
       {:error, msg} = TwitterAPI.register_user(data)
@@ -355,13 +355,13 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
         UserInviteToken.create_invite(%{expires_at: Date.add(Date.utc_today(), -1), max_use: 100})
 
       data = %{
-        "nickname" => "GrimReaper",
-        "email" => "death@reapers.afterlife",
-        "fullname" => "Reaper Grim",
-        "bio" => "Your time has come",
-        "password" => "scythe",
-        "confirm" => "scythe",
-        "token" => invite.token
+        :nickname => "GrimReaper",
+        :email => "death@reapers.afterlife",
+        :fullname => "Reaper Grim",
+        :bio => "Your time has come",
+        :password => "scythe",
+        :confirm => "scythe",
+        :token => invite.token
       }
 
       {:error, msg} = TwitterAPI.register_user(data)
@@ -377,13 +377,13 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
       UserInviteToken.update_invite!(invite, uses: 100)
 
       data = %{
-        "nickname" => "GrimReaper",
-        "email" => "death@reapers.afterlife",
-        "fullname" => "Reaper Grim",
-        "bio" => "Your time has come",
-        "password" => "scythe",
-        "confirm" => "scythe",
-        "token" => invite.token
+        :nickname => "GrimReaper",
+        :email => "death@reapers.afterlife",
+        :fullname => "Reaper Grim",
+        :bio => "Your time has come",
+        :password => "scythe",
+        :confirm => "scythe",
+        :token => invite.token
       }
 
       {:error, msg} = TwitterAPI.register_user(data)
@@ -395,11 +395,11 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
 
   test "it returns the error on registration problems" do
     data = %{
-      "nickname" => "lain",
-      "email" => "lain@wired.jp",
-      "fullname" => "lain iwakura",
-      "bio" => "close the world.",
-      "password" => "bear"
+      :nickname => "lain",
+      :email => "lain@wired.jp",
+      :fullname => "lain iwakura",
+      :bio => "close the world.",
+      :password => "bear"
     }
 
     {:error, error_object} = TwitterAPI.register_user(data)