diff options
31 files changed, 2238 insertions, 591 deletions
| diff --git a/docs/API/differences_in_mastoapi_responses.md b/docs/API/differences_in_mastoapi_responses.md index 41ceda26b..921995510 100644 --- a/docs/API/differences_in_mastoapi_responses.md +++ b/docs/API/differences_in_mastoapi_responses.md @@ -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 diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 61a4960a0..1f4a09370 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -825,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 @@ -1032,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, ^[]) @@ -1041,7 +1041,7 @@ 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) @@ -1085,7 +1085,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do    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 @@ -1164,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 diff --git a/lib/pleroma/web/api_spec.ex b/lib/pleroma/web/api_spec.ex index 3890489e3..b3c1e3ea2 100644 --- a/lib/pleroma/web/api_spec.ex +++ b/lib/pleroma/web/api_spec.ex @@ -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", diff --git a/lib/pleroma/web/api_spec/helpers.ex b/lib/pleroma/web/api_spec/helpers.ex index 7348dcbee..ce40fb9e8 100644 --- a/lib/pleroma/web/api_spec/helpers.ex +++ b/lib/pleroma/web/api_spec/helpers.ex @@ -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 index 000000000..2efe6e901 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -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 diff --git a/lib/pleroma/web/api_spec/operations/app_operation.ex b/lib/pleroma/web/api_spec/operations/app_operation.ex index 035ef2470..f6ccd073f 100644 --- a/lib/pleroma/web/api_spec/operations/app_operation.ex +++ b/lib/pleroma/web/api_spec/operations/app_operation.ex @@ -49,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{ diff --git a/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex b/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex index a117fe460..2f812ac77 100644 --- a/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex +++ b/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex @@ -5,7 +5,7 @@  defmodule Pleroma.Web.ApiSpec.CustomEmojiOperation do    alias OpenApiSpex.Operation    alias OpenApiSpex.Schema -  alias Pleroma.Web.ApiSpec.Schemas.CustomEmoji +  alias Pleroma.Web.ApiSpec.Schemas.Emoji    def open_api_operation(action) do      operation = String.to_existing_atom("#{action}_operation") @@ -19,17 +19,17 @@ 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", custom_emojis_resposnse()) +        200 => Operation.response("Custom Emojis", "application/json", resposnse())        }      }    end -  defp custom_emojis_resposnse do +  defp resposnse do      %Schema{        title: "CustomEmojisResponse",        description: "Response schema for custom emojis",        type: :array, -      items: CustomEmoji, +      items: custom_emoji(),        example: [          %{            "category" => "Fun", @@ -58,4 +58,31 @@ defmodule Pleroma.Web.ApiSpec.CustomEmojiOperation do        ]      }    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  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 index 000000000..b5877ca9c --- /dev/null +++ b/lib/pleroma/web/api_spec/render_error.ex @@ -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 index 000000000..d54e2158d --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/account.ex @@ -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 index 000000000..fa97073a0 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/account_field.ex @@ -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 index 000000000..8b982669e --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/account_relationship.ex @@ -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 index 000000000..ac9b46678 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/actor_type.ex @@ -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 diff --git a/lib/pleroma/web/api_spec/schemas/api_error.ex b/lib/pleroma/web/api_spec/schemas/api_error.ex new file mode 100644 index 000000000..5815df94c --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/api_error.ex @@ -0,0 +1,19 @@ +# 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.ApiError do +  alias OpenApiSpex.Schema + +  require OpenApiSpex + +  OpenApiSpex.schema(%{ +    title: "ApiError", +    description: "Response schema for API error", +    type: :object, +    properties: %{error: %Schema{type: :string}}, +    example: %{ +      "error" => "Something went wrong" +    } +  }) +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 index 000000000..f3bfb74da --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/boolean_like.ex @@ -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 index 5531b2081..000000000 --- a/lib/pleroma/web/api_spec/schemas/custom_emoji.ex +++ /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/emoji.ex b/lib/pleroma/web/api_spec/schemas/emoji.ex new file mode 100644 index 000000000..26f35e648 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/emoji.ex @@ -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 index 000000000..3b5f6477a --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/flake_id.ex @@ -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 index 000000000..0474b550b --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/poll.ex @@ -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 index 000000000..aef0588d4 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/status.ex @@ -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 index 000000000..8c81a4d73 --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/visibility_scope.ex @@ -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 diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex index 4780081b2..eb97ae975 100644 --- a/lib/pleroma/web/controller_helper.ex +++ b/lib/pleroma/web/controller_helper.ex @@ -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 diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 5a92cebd8..37adeec5f 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -26,6 +26,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 when action == :identity_proofs)    plug( @@ -85,26 +87,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), @@ -128,7 +130,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 @@ -150,7 +152,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, @@ -166,22 +175,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) @@ -194,7 +203,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 @@ -205,12 +214,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) @@ -220,7 +232,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) @@ -231,12 +243,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) @@ -256,6 +270,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) @@ -270,6 +289,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) @@ -296,8 +320,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}) @@ -316,10 +340,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}) @@ -356,7 +378,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do    end    @doc "POST /api/v1/follows" -  def follows(conn, %{"uri" => uri}) do +  def follows(%{body_params: %{uri: uri}} = conn, _) do      case User.get_cached_by_nickname(uri) do        %User{} = user ->          conn diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 1d9082c09..24167f66f 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -529,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: [] diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index 7a1ba6936..cf1d9c74c 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -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 @@ -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 @@ -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"}, diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex index 781622476..fa30a0c41 100644 --- a/test/support/conn_case.ex +++ b/test/support/conn_case.ex @@ -51,7 +51,19 @@ defmodule Pleroma.Web.ConnCase do          %{user: user, token: token, conn: conn}        end -      defp json_response_and_validate_schema(conn, status \\ nil) do +      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") @@ -59,10 +71,12 @@ defmodule Pleroma.Web.ConnCase do            |> String.split(";")            |> List.first() -        status = status || conn.status +        status = Plug.Conn.Status.code(status) -        %{private: %{open_api_spex: %{operation_id: op_id, operation_lookup: lookup, spec: spec}}} = -          conn +        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) @@ -87,6 +101,10 @@ defmodule Pleroma.Web.ConnCase do          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) diff --git a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs index 2d256f63c..fdb6d4c5d 100644 --- a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs +++ b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs @@ -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 diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs index 8c428efee..ba70ba66c 100644 --- a/test/web/mastodon_api/controllers/account_controller_test.exs +++ b/test/web/mastodon_api/controllers/account_controller_test.exs @@ -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 diff --git a/test/web/mastodon_api/controllers/custom_emoji_controller_test.exs b/test/web/mastodon_api/controllers/custom_emoji_controller_test.exs index 4222556a4..ab0027f90 100644 --- a/test/web/mastodon_api/controllers/custom_emoji_controller_test.exs +++ b/test/web/mastodon_api/controllers/custom_emoji_controller_test.exs @@ -4,8 +4,6 @@  defmodule Pleroma.Web.MastodonAPI.CustomEmojiControllerTest do    use Pleroma.Web.ConnCase, async: true -  alias Pleroma.Web.ApiSpec -  import OpenApiSpex.TestAssertions    test "with tags", %{conn: conn} do      assert resp = @@ -21,6 +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(emoji, "CustomEmoji", ApiSpec.spec())    end  end diff --git a/test/web/twitter_api/twitter_api_test.exs b/test/web/twitter_api/twitter_api_test.exs index f6e13b661..7926a0757 100644 --- a/test/web/twitter_api/twitter_api_test.exs +++ b/test/web/twitter_api/twitter_api_test.exs @@ -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) | 
