diff options
43 files changed, 2756 insertions, 2147 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ea4dcc72..e859e318a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,11 +16,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).  ### Changed  - **Breaking:** Elixir >=1.8 is now required (was >= 1.7) +- **Breaking:** Admin API: Return link alongside with token on password reset  - Replaced [pleroma_job_queue](https://git.pleroma.social/pleroma/pleroma_job_queue) and `Pleroma.Web.Federator.RetryQueue` with [Oban](https://github.com/sorentwo/oban) (see [`docs/config.md`](docs/config.md) on migrating customized worker / retry settings)  - Introduced [quantum](https://github.com/quantum-elixir/quantum-core) job scheduler  - Admin API: Return `total` when querying for reports  - Mastodon API: Return `pleroma.direct_conversation_id` when creating a direct message (`POST /api/v1/statuses`) -- Admin API: Return link alongside with token on password reset  - Mastodon API: notifications no longer include subscription notifications - they are now served from new endpoints in Pleroma API  ### Fixed  - Mastodon API: Fix private and direct statuses not being filtered out from the public timeline for an authenticated user (`GET /api/v1/timelines/public`) @@ -114,6 +114,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).  - Pleroma API: Add `/api/v1/pleroma/accounts/confirmation_resend?email=<email>` for resending account confirmation.  - Pleroma API: Email change endpoint.  - Admin API: Added moderation log +- Support for `X-Forwarded-For` and similar HTTP headers which used by reverse proxies to pass a real user IP address to the backend. Must not be enabled unless your instance is behind at least one reverse proxy (such as Nginx, Apache HTTPD or Varnish Cache).  - Web response cache (currently, enabled for ActivityPub)  - Mastodon API: Added an endpoint to get multiple statuses by IDs (`GET /api/v1/statuses/?ids[]=1&ids[]=2`)  - ActivityPub: Add ActivityPub actor's `discoverable` parameter. diff --git a/config/config.exs b/config/config.exs index 403ade60d..36bea19a0 100644 --- a/config/config.exs +++ b/config/config.exs @@ -591,6 +591,8 @@ config :pleroma, :rate_limit, nil  config :pleroma, Pleroma.ActivityExpiration, enabled: true +config :pleroma, Pleroma.Plugs.RemoteIp, enabled: false +  config :pleroma, :web_cache_ttl,    activity_pub: nil,    activity_pub_question: 30_000 diff --git a/config/description.exs b/config/description.exs index 38b30bbf6..4547ea368 100644 --- a/config/description.exs +++ b/config/description.exs @@ -2689,6 +2689,42 @@ config :pleroma, :config_description, [    },    %{      group: :pleroma, +    key: Pleroma.Plugs.RemoteIp, +    type: :group, +    description: """ +    **If your instance is not behind at least one reverse proxy, you should not enable this plug.** + +    `Pleroma.Plugs.RemoteIp` is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration. +    """, +    children: [ +      %{ +        key: :enabled, +        type: :boolean, +        description: "Enable/disable the plug. Defaults to `false`.", +        suggestions: [true, false] +      }, +      %{ +        key: :headers, +        type: {:list, :string}, +        description: +          "A list of strings naming the `req_headers` to use when deriving the `remote_ip`. Order does not matter. Defaults to `~w[forwarded x-forwarded-for x-client-ip x-real-ip]`." +      }, +      %{ +        key: :proxies, +        type: {:list, :string}, +        description: +          "A list of strings in [CIDR](https://en.wikipedia.org/wiki/CIDR) notation specifying the IPs of known proxies. Defaults to `[]`." +      }, +      %{ +        key: :reserved, +        type: {:list, :string}, +        description: +          "Defaults to [localhost](https://en.wikipedia.org/wiki/Localhost) and [private network](https://en.wikipedia.org/wiki/Private_network)." +      } +    ] +  }, +  %{ +    group: :pleroma,      key: :web_cache_ttl,      type: :group,      description: diff --git a/docs/api/admin_api.md b/docs/api/admin_api.md index fcdb33944..ee9e68cb1 100644 --- a/docs/api/admin_api.md +++ b/docs/api/admin_api.md @@ -312,8 +312,8 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret  ```json  { -  "token": "U13DX6muOvpRsj35_ij9wLxUbkU-eFvfKttxs6gIajo=", // password reset token (base64 string) -  "link": "https://pleroma.social/api/pleroma/password_reset/U13DX6muOvpRsj35_ij9wLxUbkU-eFvfKttxs6gIajo%3D" +  "token": "base64 reset token", +  "link": "https://pleroma.social/api/pleroma/password_reset/url-encoded-base64-token"  }  ``` @@ -330,10 +330,10 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret  ### Get a list of reports  - Method `GET`  - Params: -  - `state`: optional, the state of reports. Valid values are `open`, `closed` and `resolved` -  - `limit`: optional, the number of records to retrieve -  - `since_id`: optional, returns results that are more recent than the specified id -  - `max_id`: optional, returns results that are older than the specified id +  - *optional* `state`: **string** the state of reports. Valid values are `open`, `closed` and `resolved` +  - *optional* `limit`: **integer** the number of records to retrieve +  - *optional* `page`: **integer** page number +  - *optional* `page_size`: **integer** number of log entries per page (default is `50`)  - Response:    - On failure: 403 Forbidden error `{"error": "error_msg"}` when requested by anonymous or non-admin    - On success: JSON, returns a list of reports, where: diff --git a/docs/config.md b/docs/config.md index ed119fd32..262d15bba 100644 --- a/docs/config.md +++ b/docs/config.md @@ -730,6 +730,8 @@ This will probably take a long time.  This is an advanced feature and disabled by default. +If your instance is behind a reverse proxy you must enable and configure [`Pleroma.Plugs.RemoteIp`](#pleroma-plugs-remoteip). +  A keyword list of rate limiters where a key is a limiter name and value is the limiter configuration. The basic configuration is a tuple where:  * The first element: `scale` (Integer). The time scale in milliseconds. @@ -756,3 +758,16 @@ Available caches:  * `:activity_pub` - activity pub routes (except question activities). Defaults to `nil` (no expiration).  * `:activity_pub_question` - activity pub routes (question activities). Defaults to `30_000` (30 seconds). + +## Pleroma.Plugs.RemoteIp + +**If your instance is not behind at least one reverse proxy, you should not enable this plug.** + +`Pleroma.Plugs.RemoteIp` is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration. + +Available options: + +* `enabled` - Enable/disable the plug. Defaults to `false`. +* `headers` - A list of strings naming the `req_headers` to use when deriving the `remote_ip`. Order does not matter. Defaults to `~w[forwarded x-forwarded-for x-client-ip x-real-ip]`. +* `proxies` - A list of strings in [CIDR](https://en.wikipedia.org/wiki/CIDR) notation specifying the IPs of known proxies. Defaults to `[]`. +* `reserved` - Defaults to [localhost](https://en.wikipedia.org/wiki/Localhost) and [private network](https://en.wikipedia.org/wiki/Private_network). diff --git a/installation/pleroma.nginx b/installation/pleroma.nginx index 4da9918ca..7f48b614b 100644 --- a/installation/pleroma.nginx +++ b/installation/pleroma.nginx @@ -70,6 +70,7 @@ server {          proxy_set_header Upgrade $http_upgrade;          proxy_set_header Connection "upgrade";          proxy_set_header Host $http_host; +        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;          # this is explicitly IPv4 since Pleroma.Web.Endpoint binds on IPv4 only          # and `localhost.` resolves to [::0] on some systems: see issue #930 diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index 2c04a26f9..c1065611b 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -137,11 +137,18 @@ defmodule Pleroma.Activity do      |> Repo.one()    end +  @spec get_by_id(String.t()) :: Activity.t() | nil    def get_by_id(id) do -    Activity -    |> where([a], a.id == ^id) -    |> restrict_deactivated_users() -    |> Repo.one() +    case FlakeId.flake_id?(id) do +      true -> +        Activity +        |> where([a], a.id == ^id) +        |> restrict_deactivated_users() +        |> Repo.one() + +      _ -> +        nil +    end    end    def get_by_id_with_object(id) do diff --git a/lib/pleroma/bbs/handler.ex b/lib/pleroma/bbs/handler.ex index 0a381f592..fa838a4e4 100644 --- a/lib/pleroma/bbs/handler.ex +++ b/lib/pleroma/bbs/handler.ex @@ -42,7 +42,7 @@ defmodule Pleroma.BBS.Handler do    end    def puts_activity(activity) do -    status = Pleroma.Web.MastodonAPI.StatusView.render("status.json", %{activity: activity}) +    status = Pleroma.Web.MastodonAPI.StatusView.render("show.json", %{activity: activity})      IO.puts("-- #{status.id} by #{status.account.display_name} (#{status.account.acct})")      IO.puts(HtmlSanitizeEx.strip_tags(status.content))      IO.puts("") diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex index 3fa407931..cdfbacb0e 100644 --- a/lib/pleroma/object.ex +++ b/lib/pleroma/object.ex @@ -248,4 +248,11 @@ defmodule Pleroma.Object do        _ -> :noop      end    end + +  @doc "Updates data field of an object" +  def update_data(%Object{data: data} = object, attrs \\ %{}) do +    object +    |> Object.change(%{data: Map.merge(data || %{}, attrs)}) +    |> Repo.update() +  end  end diff --git a/lib/pleroma/plugs/remote_ip.ex b/lib/pleroma/plugs/remote_ip.ex new file mode 100644 index 000000000..fdedc27ee --- /dev/null +++ b/lib/pleroma/plugs/remote_ip.ex @@ -0,0 +1,54 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Plugs.RemoteIp do +  @moduledoc """ +  This is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration. +  """ + +  @behaviour Plug + +  @headers ~w[ +    forwarded +    x-forwarded-for +    x-client-ip +    x-real-ip +  ] + +  # https://en.wikipedia.org/wiki/Localhost +  # https://en.wikipedia.org/wiki/Private_network +  @reserved ~w[ +    127.0.0.0/8 +    ::1/128 +    fc00::/7 +    10.0.0.0/8 +    172.16.0.0/12 +    192.168.0.0/16 +  ] + +  def init(_), do: nil + +  def call(conn, _) do +    config = Pleroma.Config.get(__MODULE__, []) + +    if Keyword.get(config, :enabled, false) do +      RemoteIp.call(conn, remote_ip_opts(config)) +    else +      conn +    end +  end + +  defp remote_ip_opts(config) do +    headers = config |> Keyword.get(:headers, @headers) |> MapSet.new() +    reserved = Keyword.get(config, :reserved, @reserved) + +    proxies = +      config +      |> Keyword.get(:proxies, []) +      |> Enum.concat(reserved) +      |> Enum.map(&InetCidr.parse/1) + +    {headers, proxies} +  end +end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index f3dcf7ad4..4c1cdd042 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -505,6 +505,11 @@ defmodule Pleroma.User do      |> Repo.all()    end +  def get_all_by_ids(ids) do +    from(u in __MODULE__, where: u.id in ^ids) +    |> Repo.all() +  end +    # This is mostly an SPC migration fix. This guesses the user nickname by taking the last part    # of the ap_id and the domain and tries to get that user    def get_by_guessed_nickname(ap_id) do @@ -765,6 +770,19 @@ defmodule Pleroma.User do      update_info(user, &User.Info.set_note_count(&1, note_count))    end +  def update_mascot(user, url) do +    info_changeset = +      User.Info.mascot_update( +        user.info, +        url +      ) + +    user +    |> change() +    |> put_embed(:info, info_changeset) +    |> update_and_set_cache() +  end +    @spec maybe_fetch_follow_information(User.t()) :: User.t()    def maybe_fetch_follow_information(user) do      with {:ok, user} <- fetch_follow_information(user) do diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 90aef99f7..21da8a7ff 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -513,7 +513,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do        conn        |> put_view(StatusView) -      |> render("status.json", %{activity: activity}) +      |> render("show.json", %{activity: activity})      else        true ->          {:param_cast, nil} @@ -537,7 +537,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do        conn        |> put_view(StatusView) -      |> render("status.json", %{activity: activity}) +      |> render("show.json", %{activity: activity})      end    end diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex index aa7c8c381..f7da81b34 100644 --- a/lib/pleroma/web/common_api/activity_draft.ex +++ b/lib/pleroma/web/common_api/activity_draft.ex @@ -40,11 +40,11 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do      |> put_params(params)      |> status()      |> summary() +    |> with_valid(&attachments/1)      |> full_payload()      |> expires_at()      |> poll()      |> with_valid(&in_reply_to/1) -    |> with_valid(&attachments/1)      |> with_valid(&in_reply_to_conversation/1)      |> with_valid(&visibility/1)      |> content() diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index eb805e853..2212e93f4 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -97,10 +97,7 @@ defmodule Pleroma.Web.Endpoint do      extra: extra    ) -  # Note: the plug and its configuration is compile-time this can't be upstreamed yet -  if proxies = Pleroma.Config.get([__MODULE__, :reverse_proxies]) do -    plug(RemoteIp, proxies: proxies) -  end +  plug(Pleroma.Plugs.RemoteIp)    defmodule Instrumenter do      use Prometheus.PhoenixInstrumenter diff --git a/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex b/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex new file mode 100644 index 000000000..03db6c9b8 --- /dev/null +++ b/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex @@ -0,0 +1,26 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.DomainBlockController do +  use Pleroma.Web, :controller + +  alias Pleroma.User + +  @doc "GET /api/v1/domain_blocks" +  def index(%{assigns: %{user: %{info: info}}} = conn, _) do +    json(conn, Map.get(info, :domain_blocks, [])) +  end + +  @doc "POST /api/v1/domain_blocks" +  def create(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do +    User.block_domain(blocker, domain) +    json(conn, %{}) +  end + +  @doc "DELETE /api/v1/domain_blocks" +  def delete(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do +    User.unblock_domain(blocker, domain) +    json(conn, %{}) +  end +end diff --git a/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex b/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex new file mode 100644 index 000000000..19041304e --- /dev/null +++ b/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex @@ -0,0 +1,72 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.FilterController do +  use Pleroma.Web, :controller + +  alias Pleroma.Filter + +  @doc "GET /api/v1/filters" +  def index(%{assigns: %{user: user}} = conn, _) do +    filters = Filter.get_filters(user) + +    render(conn, "filters.json", filters: filters) +  end + +  @doc "POST /api/v1/filters" +  def create( +        %{assigns: %{user: user}} = conn, +        %{"phrase" => phrase, "context" => context} = params +      ) do +    query = %Filter{ +      user_id: user.id, +      phrase: phrase, +      context: context, +      hide: Map.get(params, "irreversible", false), +      whole_word: Map.get(params, "boolean", true) +      # expires_at +    } + +    {:ok, response} = Filter.create(query) + +    render(conn, "filter.json", filter: response) +  end + +  @doc "GET /api/v1/filters/:id" +  def show(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do +    filter = Filter.get(filter_id, user) + +    render(conn, "filter.json", filter: filter) +  end + +  @doc "PUT /api/v1/filters/:id" +  def update( +        %{assigns: %{user: user}} = conn, +        %{"phrase" => phrase, "context" => context, "id" => filter_id} = params +      ) do +    query = %Filter{ +      user_id: user.id, +      filter_id: filter_id, +      phrase: phrase, +      context: context, +      hide: Map.get(params, "irreversible", nil), +      whole_word: Map.get(params, "boolean", true) +      # expires_at +    } + +    {:ok, response} = Filter.update(query) +    render(conn, "filter.json", filter: response) +  end + +  @doc "DELETE /api/v1/filters/:id" +  def delete(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do +    query = %Filter{ +      user_id: user.id, +      filter_id: filter_id +    } + +    {:ok, _} = Filter.delete(query) +    json(conn, %{}) +  end +end diff --git a/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex new file mode 100644 index 000000000..267014b97 --- /dev/null +++ b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex @@ -0,0 +1,49 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.FollowRequestController do +  use Pleroma.Web, :controller + +  alias Pleroma.User +  alias Pleroma.Web.CommonAPI + +  plug(:put_view, Pleroma.Web.MastodonAPI.AccountView) +  plug(:assign_follower when action != :index) + +  action_fallback(:errors) + +  @doc "GET /api/v1/follow_requests" +  def index(%{assigns: %{user: followed}} = conn, _params) do +    follow_requests = User.get_follow_requests(followed) + +    render(conn, "accounts.json", for: followed, users: follow_requests, as: :user) +  end + +  @doc "POST /api/v1/follow_requests/:id/authorize" +  def authorize(%{assigns: %{user: followed, follower: follower}} = conn, _params) do +    with {:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do +      render(conn, "relationship.json", user: followed, target: follower) +    end +  end + +  @doc "POST /api/v1/follow_requests/:id/reject" +  def reject(%{assigns: %{user: followed, follower: follower}} = conn, _params) do +    with {:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do +      render(conn, "relationship.json", user: followed, target: follower) +    end +  end + +  defp assign_follower(%{params: %{"id" => id}} = conn, _) do +    case User.get_cached_by_id(id) do +      %User{} = follower -> assign(conn, :follower, follower) +      nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt() +    end +  end + +  defp errors(conn, {:error, message}) do +    conn +    |> put_status(:forbidden) +    |> json(%{error: message}) +  end +end diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index e4ae63231..0878f7ba6 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -14,13 +14,11 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    alias Pleroma.Config    alias Pleroma.Conversation.Participation    alias Pleroma.Emoji -  alias Pleroma.Filter    alias Pleroma.HTTP    alias Pleroma.Object    alias Pleroma.Pagination    alias Pleroma.Plugs.RateLimiter    alias Pleroma.Repo -  alias Pleroma.ScheduledActivity    alias Pleroma.Stats    alias Pleroma.User    alias Pleroma.Web @@ -30,12 +28,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    alias Pleroma.Web.MastodonAPI.AccountView    alias Pleroma.Web.MastodonAPI.AppView    alias Pleroma.Web.MastodonAPI.ConversationView -  alias Pleroma.Web.MastodonAPI.FilterView    alias Pleroma.Web.MastodonAPI.ListView    alias Pleroma.Web.MastodonAPI.MastodonAPI    alias Pleroma.Web.MastodonAPI.MastodonView    alias Pleroma.Web.MastodonAPI.ReportView -  alias Pleroma.Web.MastodonAPI.ScheduledActivityView    alias Pleroma.Web.MastodonAPI.StatusView    alias Pleroma.Web.MediaProxy    alias Pleroma.Web.OAuth.App @@ -44,35 +40,17 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    alias Pleroma.Web.OAuth.Token    alias Pleroma.Web.TwitterAPI.TwitterAPI -  import Ecto.Query -    require Logger    require Pleroma.Constants    @rate_limited_relations_actions ~w(follow unfollow)a -  @rate_limited_status_actions ~w(reblog_status unreblog_status fav_status unfav_status -    post_status delete_status)a - -  plug( -    RateLimiter, -    {:status_id_action, bucket_name: "status_id_action:reblog_unreblog", params: ["id"]} -    when action in ~w(reblog_status unreblog_status)a -  ) - -  plug( -    RateLimiter, -    {:status_id_action, bucket_name: "status_id_action:fav_unfav", params: ["id"]} -    when action in ~w(fav_status unfav_status)a -  ) -    plug(      RateLimiter,      {:relations_id_action, params: ["id", "uri"]} when action in @rate_limited_relations_actions    )    plug(RateLimiter, :relations_actions when action in @rate_limited_relations_actions) -  plug(RateLimiter, :statuses_actions when action in @rate_limited_status_actions)    plug(RateLimiter, :app_account_creation when action == :account_register)    plug(RateLimiter, :search when action in [:search, :search2, :account_search])    plug(RateLimiter, :password_reset when action == :password_reset) @@ -362,63 +340,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do      end    end -  def get_statuses(%{assigns: %{user: user}} = conn, %{"ids" => ids}) do -    limit = 100 - -    activities = -      ids -      |> Enum.take(limit) -      |> Activity.all_by_ids_with_object() -      |> Enum.filter(&Visibility.visible_for_user?(&1, user)) - -    conn -    |> put_view(StatusView) -    |> render("index.json", activities: activities, for: user, as: :activity) -  end - -  def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do -    with %Activity{} = activity <- Activity.get_by_id_with_object(id), -         true <- Visibility.visible_for_user?(activity, user) do -      conn -      |> put_view(StatusView) -      |> try_render("status.json", %{activity: activity, for: user}) -    end -  end - -  def get_context(%{assigns: %{user: user}} = conn, %{"id" => id}) do -    with %Activity{} = activity <- Activity.get_by_id(id), -         activities <- -           ActivityPub.fetch_activities_for_context(activity.data["context"], %{ -             "blocking_user" => user, -             "user" => user, -             "exclude_id" => activity.id -           }), -         grouped_activities <- Enum.group_by(activities, fn %{id: id} -> id < activity.id end) do -      result = %{ -        ancestors: -          StatusView.render( -            "index.json", -            for: user, -            activities: grouped_activities[true] || [], -            as: :activity -          ) -          |> Enum.reverse(), -        # credo:disable-for-previous-line Credo.Check.Refactor.PipeChainStart -        descendants: -          StatusView.render( -            "index.json", -            for: user, -            activities: grouped_activities[false] || [], -            as: :activity -          ) -          |> Enum.reverse() -        # credo:disable-for-previous-line Credo.Check.Refactor.PipeChainStart -      } - -      json(conn, result) -    end -  end -    def get_poll(%{assigns: %{user: user}} = conn, %{"id" => id}) do      with %Object{} = object <- Object.get_by_id_and_maybe_refetch(id, interval: 60),           %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]), @@ -469,196 +390,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do      end    end -  def scheduled_statuses(%{assigns: %{user: user}} = conn, params) do -    with scheduled_activities <- MastodonAPI.get_scheduled_activities(user, params) do -      conn -      |> add_link_headers(scheduled_activities) -      |> put_view(ScheduledActivityView) -      |> render("index.json", %{scheduled_activities: scheduled_activities}) -    end -  end - -  def show_scheduled_status(%{assigns: %{user: user}} = conn, %{"id" => scheduled_activity_id}) do -    with %ScheduledActivity{} = scheduled_activity <- -           ScheduledActivity.get(user, scheduled_activity_id) do -      conn -      |> put_view(ScheduledActivityView) -      |> render("show.json", %{scheduled_activity: scheduled_activity}) -    else -      _ -> {:error, :not_found} -    end -  end - -  def update_scheduled_status( -        %{assigns: %{user: user}} = conn, -        %{"id" => scheduled_activity_id} = params -      ) do -    with %ScheduledActivity{} = scheduled_activity <- -           ScheduledActivity.get(user, scheduled_activity_id), -         {:ok, scheduled_activity} <- ScheduledActivity.update(scheduled_activity, params) do -      conn -      |> put_view(ScheduledActivityView) -      |> render("show.json", %{scheduled_activity: scheduled_activity}) -    else -      nil -> {:error, :not_found} -      error -> error -    end -  end - -  def delete_scheduled_status(%{assigns: %{user: user}} = conn, %{"id" => scheduled_activity_id}) do -    with %ScheduledActivity{} = scheduled_activity <- -           ScheduledActivity.get(user, scheduled_activity_id), -         {:ok, scheduled_activity} <- ScheduledActivity.delete(scheduled_activity) do -      conn -      |> put_view(ScheduledActivityView) -      |> render("show.json", %{scheduled_activity: scheduled_activity}) -    else -      nil -> {:error, :not_found} -      error -> error -    end -  end - -  def post_status( -        %{assigns: %{user: user}} = conn, -        %{"status" => _, "scheduled_at" => scheduled_at} = params -      ) do -    if ScheduledActivity.far_enough?(scheduled_at) do -      with {:ok, scheduled_activity} <- -             ScheduledActivity.create(user, %{"params" => params, "scheduled_at" => scheduled_at}) do -        conn -        |> put_view(ScheduledActivityView) -        |> render("show.json", %{scheduled_activity: scheduled_activity}) -      end -    else -      post_status(conn, Map.drop(params, ["scheduled_at"])) -    end -  end - -  def post_status(%{assigns: %{user: user}} = conn, %{"status" => _} = params) do -    case CommonAPI.post(user, params) do -      {:ok, activity} -> -        conn -        |> put_view(StatusView) -        |> try_render("status.json", %{ -          activity: activity, -          for: user, -          as: :activity, -          with_direct_conversation_id: true -        }) - -      {:error, message} -> -        conn -        |> put_status(:unprocessable_entity) -        |> json(%{error: message}) -    end -  end - -  def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do -    with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do -      json(conn, %{}) -    else -      _e -> render_error(conn, :forbidden, "Can't delete this post") -    end -  end - -  def reblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do -    with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user), -         %Activity{} = announce <- Activity.normalize(announce.data) do -      conn -      |> put_view(StatusView) -      |> try_render("status.json", %{activity: announce, for: user, as: :activity}) -    end -  end - -  def unreblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do -    with {:ok, _unannounce, %{data: %{"id" => id}}} <- CommonAPI.unrepeat(ap_id_or_id, user), -         %Activity{} = activity <- Activity.get_create_by_object_ap_id_with_object(id) do -      conn -      |> put_view(StatusView) -      |> try_render("status.json", %{activity: activity, for: user, as: :activity}) -    end -  end - -  def fav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do -    with {:ok, _fav, %{data: %{"id" => id}}} <- CommonAPI.favorite(ap_id_or_id, user), -         %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do -      conn -      |> put_view(StatusView) -      |> try_render("status.json", %{activity: activity, for: user, as: :activity}) -    end -  end - -  def unfav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do -    with {:ok, _, _, %{data: %{"id" => id}}} <- CommonAPI.unfavorite(ap_id_or_id, user), -         %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do -      conn -      |> put_view(StatusView) -      |> try_render("status.json", %{activity: activity, for: user, as: :activity}) -    end -  end - -  def pin_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do -    with {:ok, activity} <- CommonAPI.pin(ap_id_or_id, user) do -      conn -      |> put_view(StatusView) -      |> try_render("status.json", %{activity: activity, for: user, as: :activity}) -    end -  end - -  def unpin_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do -    with {:ok, activity} <- CommonAPI.unpin(ap_id_or_id, user) do -      conn -      |> put_view(StatusView) -      |> try_render("status.json", %{activity: activity, for: user, as: :activity}) -    end -  end - -  def bookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do -    with %Activity{} = activity <- Activity.get_by_id_with_object(id), -         %User{} = user <- User.get_cached_by_nickname(user.nickname), -         true <- Visibility.visible_for_user?(activity, user), -         {:ok, _bookmark} <- Bookmark.create(user.id, activity.id) do -      conn -      |> put_view(StatusView) -      |> try_render("status.json", %{activity: activity, for: user, as: :activity}) -    end -  end - -  def unbookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do -    with %Activity{} = activity <- Activity.get_by_id_with_object(id), -         %User{} = user <- User.get_cached_by_nickname(user.nickname), -         true <- Visibility.visible_for_user?(activity, user), -         {:ok, _bookmark} <- Bookmark.destroy(user.id, activity.id) do -      conn -      |> put_view(StatusView) -      |> try_render("status.json", %{activity: activity, for: user, as: :activity}) -    end -  end - -  def mute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do -    activity = Activity.get_by_id(id) - -    with {:ok, activity} <- CommonAPI.add_mute(user, activity) do -      conn -      |> put_view(StatusView) -      |> try_render("status.json", %{activity: activity, for: user, as: :activity}) -    end -  end - -  def unmute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do -    activity = Activity.get_by_id(id) - -    with {:ok, activity} <- CommonAPI.remove_mute(user, activity) do -      conn -      |> put_view(StatusView) -      |> try_render("status.json", %{activity: activity, for: user, as: :activity}) -    end -  end -    def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do -    id = List.wrap(id) -    q = from(u in User, where: u.id in ^id) -    targets = Repo.all(q) +    targets = User.get_all_by_ids(List.wrap(id))      conn      |> put_view(AccountView) @@ -668,19 +401,15 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    # Instead of returning a 400 when no "id" params is present, Mastodon returns an empty array.    def relationships(%{assigns: %{user: _user}} = conn, _), do: json(conn, []) -  def update_media(%{assigns: %{user: user}} = conn, data) do -    with %Object{} = object <- Repo.get(Object, data["id"]), +  def update_media( +        %{assigns: %{user: user}} = conn, +        %{"id" => id, "description" => description} = _ +      ) +      when is_binary(description) do +    with %Object{} = object <- Repo.get(Object, id),           true <- Object.authorize_mutation(object, user), -         true <- is_binary(data["description"]), -         description <- data["description"] do -      new_data = %{object.data | "name" => description} - -      {:ok, _} = -        object -        |> Object.change(%{data: new_data}) -        |> Repo.update() - -      attachment_data = Map.put(new_data, "id", object.id) +         {:ok, %Object{data: data}} <- Object.update_data(object, %{"name" => description}) do +      attachment_data = Map.put(data, "id", object.id)        conn        |> put_view(StatusView) @@ -688,6 +417,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do      end    end +  def update_media(_conn, _data), do: {:error, :bad_request} +    def upload(%{assigns: %{user: user}} = conn, %{"file" => file} = data) do      with {:ok, object} <-             ActivityPub.upload( @@ -722,46 +453,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    def get_mascot(%{assigns: %{user: user}} = conn, _params) do      mascot = User.get_mascot(user) -    conn -    |> json(mascot) -  end - -  def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do -    with %Activity{} = activity <- Activity.get_by_id_with_object(id), -         {:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)}, -         %Object{data: %{"likes" => likes}} <- Object.normalize(activity) do -      q = from(u in User, where: u.ap_id in ^likes) - -      users = -        Repo.all(q) -        |> Enum.filter(&(not User.blocks?(user, &1))) - -      conn -      |> put_view(AccountView) -      |> render("accounts.json", %{for: user, users: users, as: :user}) -    else -      {:visible, false} -> {:error, :not_found} -      _ -> json(conn, []) -    end -  end - -  def reblogged_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do -    with %Activity{} = activity <- Activity.get_by_id_with_object(id), -         {:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)}, -         %Object{data: %{"announcements" => announces}} <- Object.normalize(activity) do -      q = from(u in User, where: u.ap_id in ^announces) - -      users = -        Repo.all(q) -        |> Enum.filter(&(not User.blocks?(user, &1))) - -      conn -      |> put_view(AccountView) -      |> render("accounts.json", %{for: user, users: users, as: :user}) -    else -      {:visible, false} -> {:error, :not_found} -      _ -> json(conn, []) -    end +    json(conn, mascot)    end    def followers(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do @@ -798,42 +490,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do      end    end -  def follow_requests(%{assigns: %{user: followed}} = conn, _params) do -    follow_requests = User.get_follow_requests(followed) - -    conn -    |> put_view(AccountView) -    |> render("accounts.json", %{for: followed, users: follow_requests, as: :user}) -  end - -  def authorize_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do -    with %User{} = follower <- User.get_cached_by_id(id), -         {:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do -      conn -      |> put_view(AccountView) -      |> render("relationship.json", %{user: followed, target: follower}) -    else -      {:error, message} -> -        conn -        |> put_status(:forbidden) -        |> json(%{error: message}) -    end -  end - -  def reject_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do -    with %User{} = follower <- User.get_cached_by_id(id), -         {:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do -      conn -      |> put_view(AccountView) -      |> render("relationship.json", %{user: followed, target: follower}) -    else -      {:error, message} -> -        conn -        |> put_status(:forbidden) -        |> json(%{error: message}) -    end -  end -    def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do      with {_, %User{} = followed} <- {:followed, User.get_cached_by_id(id)},           {_, true} <- {:followed, follower.id != followed.id}, @@ -963,20 +619,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do      end    end -  def domain_blocks(%{assigns: %{user: %{info: info}}} = conn, _) do -    json(conn, info.domain_blocks || []) -  end - -  def block_domain(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do -    User.block_domain(blocker, domain) -    json(conn, %{}) -  end - -  def unblock_domain(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do -    User.unblock_domain(blocker, domain) -    json(conn, %{}) -  end -    def subscribe(%{assigns: %{user: user}} = conn, %{"id" => id}) do      with %User{} = subscription_target <- User.get_cached_by_id(id),           {:ok, subscription_target} = User.subscribe(user, subscription_target) do @@ -984,10 +626,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do        |> put_view(AccountView)        |> render("relationship.json", %{user: user, target: subscription_target})      else -      {:error, message} -> -        conn -        |> put_status(:forbidden) -        |> json(%{error: message}) +      nil -> {:error, :not_found} +      e -> e      end    end @@ -998,10 +638,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do        |> put_view(AccountView)        |> render("relationship.json", %{user: user, target: subscription_target})      else -      {:error, message} -> -        conn -        |> put_status(:forbidden) -        |> json(%{error: message}) +      nil -> {:error, :not_found} +      e -> e      end    end @@ -1072,8 +710,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    def account_lists(%{assigns: %{user: user}} = conn, %{"id" => account_id}) do      lists = Pleroma.List.get_lists_account_belongs(user, account_id) -    res = ListView.render("lists.json", lists: lists) -    json(conn, res) + +    conn +    |> put_view(ListView) +    |> render("index.json", %{lists: lists})    end    def index(%{assigns: %{user: user}} = conn, _params) do @@ -1199,7 +839,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    @doc "Local Mastodon FE login init action"    def login(conn, %{"code" => auth_token}) do      with {:ok, app} <- get_or_make_app(), -         %Authorization{} = auth <- Repo.get_by(Authorization, token: auth_token, app_id: app.id), +         {:ok, auth} <- Authorization.get_by_token(app, auth_token),           {:ok, token} <- Token.exchange_token(app, auth) do        conn        |> put_session(:oauth_token, token.token) @@ -1211,9 +851,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    def login(conn, _) do      with {:ok, app} <- get_or_make_app() do        path = -        o_auth_path( -          conn, -          :authorize, +        o_auth_path(conn, :authorize,            response_type: "code",            client_id: app.client_id,            redirect_uri: ".", @@ -1235,31 +873,12 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do      end    end +  @spec get_or_make_app() :: {:ok, App.t()} | {:error, Ecto.Changeset.t()}    defp get_or_make_app do -    find_attrs = %{client_name: @local_mastodon_name, redirect_uris: "."} -    scopes = ["read", "write", "follow", "push"] - -    with %App{} = app <- Repo.get_by(App, find_attrs) do -      {:ok, app} = -        if app.scopes == scopes do -          {:ok, app} -        else -          app -          |> Changeset.change(%{scopes: scopes}) -          |> Repo.update() -        end - -      {:ok, app} -    else -      _e -> -        cs = -          App.register_changeset( -            %App{}, -            Map.put(find_attrs, :scopes, scopes) -          ) - -        Repo.insert(cs) -    end +    App.get_or_make( +      %{client_name: @local_mastodon_name, redirect_uris: "."}, +      ["read", "write", "follow", "push"] +    )    end    def logout(conn, _) do @@ -1268,16 +887,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do      |> redirect(to: "/")    end -  def relationship_noop(%{assigns: %{user: user}} = conn, %{"id" => id}) do -    Logger.debug("Unimplemented, returning unmodified relationship") - -    with %User{} = target <- User.get_cached_by_id(id) do -      conn -      |> put_view(AccountView) -      |> render("relationship.json", %{user: user, target: target}) -    end -  end - +  # Stubs for unimplemented mastodon api +  #    def empty_array(conn, _) do      Logger.debug("Unimplemented, returning an empty array")      json(conn, []) @@ -1288,65 +899,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do      json(conn, %{})    end -  def get_filters(%{assigns: %{user: user}} = conn, _) do -    filters = Filter.get_filters(user) -    res = FilterView.render("filters.json", filters: filters) -    json(conn, res) -  end - -  def create_filter( -        %{assigns: %{user: user}} = conn, -        %{"phrase" => phrase, "context" => context} = params -      ) do -    query = %Filter{ -      user_id: user.id, -      phrase: phrase, -      context: context, -      hide: Map.get(params, "irreversible", false), -      whole_word: Map.get(params, "boolean", true) -      # expires_at -    } - -    {:ok, response} = Filter.create(query) -    res = FilterView.render("filter.json", filter: response) -    json(conn, res) -  end - -  def get_filter(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do -    filter = Filter.get(filter_id, user) -    res = FilterView.render("filter.json", filter: filter) -    json(conn, res) -  end - -  def update_filter( -        %{assigns: %{user: user}} = conn, -        %{"phrase" => phrase, "context" => context, "id" => filter_id} = params -      ) do -    query = %Filter{ -      user_id: user.id, -      filter_id: filter_id, -      phrase: phrase, -      context: context, -      hide: Map.get(params, "irreversible", nil), -      whole_word: Map.get(params, "boolean", true) -      # expires_at -    } - -    {:ok, response} = Filter.update(query) -    res = FilterView.render("filter.json", filter: response) -    json(conn, res) -  end - -  def delete_filter(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do -    query = %Filter{ -      user_id: user.id, -      filter_id: filter_id -    } - -    {:ok, _} = Filter.delete(query) -    json(conn, %{}) -  end -    def suggestions(%{assigns: %{user: user}} = conn, _) do      suggestions = Config.get(:suggestions) @@ -1394,22 +946,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do      end    end -  def status_card(%{assigns: %{user: user}} = conn, %{"id" => status_id}) do -    with %Activity{} = activity <- Activity.get_by_id(status_id), -         true <- Visibility.visible_for_user?(activity, user) do -      data = -        StatusView.render( -          "card.json", -          Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) -        ) - -      json(conn, data) -    else -      _e -> -        %{} -    end -  end -    def reports(%{assigns: %{user: user}} = conn, params) do      case CommonAPI.report(user, params) do        {:ok, activity} -> @@ -1459,7 +995,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do      end    end -  def account_register(%{assigns: %{app: _app}} = conn, _params) do +  def account_register(%{assigns: %{app: _app}} = conn, _) do      render_error(conn, :bad_request, "Missing parameters")    end diff --git a/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex b/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex new file mode 100644 index 000000000..0a56b10b6 --- /dev/null +++ b/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex @@ -0,0 +1,51 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.ScheduledActivityController do +  use Pleroma.Web, :controller + +  import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] + +  alias Pleroma.ScheduledActivity +  alias Pleroma.Web.MastodonAPI.MastodonAPI + +  plug(:assign_scheduled_activity when action != :index) + +  action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + +  @doc "GET /api/v1/scheduled_statuses" +  def index(%{assigns: %{user: user}} = conn, params) do +    with scheduled_activities <- MastodonAPI.get_scheduled_activities(user, params) do +      conn +      |> add_link_headers(scheduled_activities) +      |> render("index.json", scheduled_activities: scheduled_activities) +    end +  end + +  @doc "GET /api/v1/scheduled_statuses/:id" +  def show(%{assigns: %{scheduled_activity: scheduled_activity}} = conn, _params) do +    render(conn, "show.json", scheduled_activity: scheduled_activity) +  end + +  @doc "PUT /api/v1/scheduled_statuses/:id" +  def update(%{assigns: %{scheduled_activity: scheduled_activity}} = conn, params) do +    with {:ok, scheduled_activity} <- ScheduledActivity.update(scheduled_activity, params) do +      render(conn, "show.json", scheduled_activity: scheduled_activity) +    end +  end + +  @doc "DELETE /api/v1/scheduled_statuses/:id" +  def delete(%{assigns: %{scheduled_activity: scheduled_activity}} = conn, _params) do +    with {:ok, scheduled_activity} <- ScheduledActivity.delete(scheduled_activity) do +      render(conn, "show.json", scheduled_activity: scheduled_activity) +    end +  end + +  defp assign_scheduled_activity(%{assigns: %{user: user}, params: %{"id" => id}} = conn, _) do +    case ScheduledActivity.get(user, id) do +      %ScheduledActivity{} = activity -> assign(conn, :scheduled_activity, activity) +      nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt() +    end +  end +end diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex new file mode 100644 index 000000000..f4de9285b --- /dev/null +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -0,0 +1,274 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.StatusController do +  use Pleroma.Web, :controller + +  import Pleroma.Web.MastodonAPI.MastodonAPIController, only: [try_render: 3] + +  require Ecto.Query + +  alias Pleroma.Activity +  alias Pleroma.Bookmark +  alias Pleroma.Object +  alias Pleroma.Plugs.RateLimiter +  alias Pleroma.Repo +  alias Pleroma.ScheduledActivity +  alias Pleroma.User +  alias Pleroma.Web.ActivityPub.ActivityPub +  alias Pleroma.Web.ActivityPub.Visibility +  alias Pleroma.Web.CommonAPI +  alias Pleroma.Web.MastodonAPI.AccountView +  alias Pleroma.Web.MastodonAPI.ScheduledActivityView + +  @rate_limited_status_actions ~w(reblog unreblog favourite unfavourite create delete)a + +  plug( +    RateLimiter, +    {:status_id_action, bucket_name: "status_id_action:reblog_unreblog", params: ["id"]} +    when action in ~w(reblog unreblog)a +  ) + +  plug( +    RateLimiter, +    {:status_id_action, bucket_name: "status_id_action:fav_unfav", params: ["id"]} +    when action in ~w(favourite unfavourite)a +  ) + +  plug(RateLimiter, :statuses_actions when action in @rate_limited_status_actions) + +  action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + +  @doc """ +  GET `/api/v1/statuses?ids[]=1&ids[]=2` + +  `ids` query param is required +  """ +  def index(%{assigns: %{user: user}} = conn, %{"ids" => ids}) do +    limit = 100 + +    activities = +      ids +      |> Enum.take(limit) +      |> Activity.all_by_ids_with_object() +      |> Enum.filter(&Visibility.visible_for_user?(&1, user)) + +    render(conn, "index.json", activities: activities, for: user, as: :activity) +  end + +  @doc """ +  POST /api/v1/statuses + +  Creates a scheduled status when `scheduled_at` param is present and it's far enough +  """ +  def create( +        %{assigns: %{user: user}} = conn, +        %{"status" => _, "scheduled_at" => scheduled_at} = params +      ) do +    params = Map.put(params, "in_reply_to_status_id", params["in_reply_to_id"]) + +    if ScheduledActivity.far_enough?(scheduled_at) do +      with {:ok, scheduled_activity} <- +             ScheduledActivity.create(user, %{"params" => params, "scheduled_at" => scheduled_at}) do +        conn +        |> put_view(ScheduledActivityView) +        |> render("show.json", scheduled_activity: scheduled_activity) +      end +    else +      create(conn, Map.drop(params, ["scheduled_at"])) +    end +  end + +  @doc """ +  POST /api/v1/statuses + +  Creates a regular status +  """ +  def create(%{assigns: %{user: user}} = conn, %{"status" => _} = params) do +    params = Map.put(params, "in_reply_to_status_id", params["in_reply_to_id"]) + +    with {:ok, activity} <- CommonAPI.post(user, params) do +      try_render(conn, "show.json", +        activity: activity, +        for: user, +        as: :activity, +        with_direct_conversation_id: true +      ) +    else +      {:error, message} -> +        conn +        |> put_status(:unprocessable_entity) +        |> json(%{error: message}) +    end +  end + +  def create(%{assigns: %{user: _user}} = conn, %{"media_ids" => _} = params) do +    create(conn, Map.put(params, "status", "")) +  end + +  @doc "GET /api/v1/statuses/:id" +  def show(%{assigns: %{user: user}} = conn, %{"id" => id}) do +    with %Activity{} = activity <- Activity.get_by_id_with_object(id), +         true <- Visibility.visible_for_user?(activity, user) do +      try_render(conn, "show.json", activity: activity, for: user) +    end +  end + +  @doc "DELETE /api/v1/statuses/:id" +  def delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do +    with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do +      json(conn, %{}) +    else +      _e -> render_error(conn, :forbidden, "Can't delete this post") +    end +  end + +  @doc "POST /api/v1/statuses/:id/reblog" +  def reblog(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do +    with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user), +         %Activity{} = announce <- Activity.normalize(announce.data) do +      try_render(conn, "show.json", %{activity: announce, for: user, as: :activity}) +    end +  end + +  @doc "POST /api/v1/statuses/:id/unreblog" +  def unreblog(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do +    with {:ok, _unannounce, %{data: %{"id" => id}}} <- CommonAPI.unrepeat(ap_id_or_id, user), +         %Activity{} = activity <- Activity.get_create_by_object_ap_id_with_object(id) do +      try_render(conn, "show.json", %{activity: activity, for: user, as: :activity}) +    end +  end + +  @doc "POST /api/v1/statuses/:id/favourite" +  def favourite(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do +    with {:ok, _fav, %{data: %{"id" => id}}} <- CommonAPI.favorite(ap_id_or_id, user), +         %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do +      try_render(conn, "show.json", activity: activity, for: user, as: :activity) +    end +  end + +  @doc "POST /api/v1/statuses/:id/unfavourite" +  def unfavourite(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do +    with {:ok, _, _, %{data: %{"id" => id}}} <- CommonAPI.unfavorite(ap_id_or_id, user), +         %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do +      try_render(conn, "show.json", activity: activity, for: user, as: :activity) +    end +  end + +  @doc "POST /api/v1/statuses/:id/pin" +  def pin(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do +    with {:ok, activity} <- CommonAPI.pin(ap_id_or_id, user) do +      try_render(conn, "show.json", activity: activity, for: user, as: :activity) +    end +  end + +  @doc "POST /api/v1/statuses/:id/unpin" +  def unpin(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do +    with {:ok, activity} <- CommonAPI.unpin(ap_id_or_id, user) do +      try_render(conn, "show.json", activity: activity, for: user, as: :activity) +    end +  end + +  @doc "POST /api/v1/statuses/:id/bookmark" +  def bookmark(%{assigns: %{user: user}} = conn, %{"id" => id}) do +    with %Activity{} = activity <- Activity.get_by_id_with_object(id), +         %User{} = user <- User.get_cached_by_nickname(user.nickname), +         true <- Visibility.visible_for_user?(activity, user), +         {:ok, _bookmark} <- Bookmark.create(user.id, activity.id) do +      try_render(conn, "show.json", activity: activity, for: user, as: :activity) +    end +  end + +  @doc "POST /api/v1/statuses/:id/unbookmark" +  def unbookmark(%{assigns: %{user: user}} = conn, %{"id" => id}) do +    with %Activity{} = activity <- Activity.get_by_id_with_object(id), +         %User{} = user <- User.get_cached_by_nickname(user.nickname), +         true <- Visibility.visible_for_user?(activity, user), +         {:ok, _bookmark} <- Bookmark.destroy(user.id, activity.id) do +      try_render(conn, "show.json", activity: activity, for: user, as: :activity) +    end +  end + +  @doc "POST /api/v1/statuses/:id/mute" +  def mute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do +    with %Activity{} = activity <- Activity.get_by_id(id), +         {:ok, activity} <- CommonAPI.add_mute(user, activity) do +      try_render(conn, "show.json", activity: activity, for: user, as: :activity) +    end +  end + +  @doc "POST /api/v1/statuses/:id/unmute" +  def unmute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do +    with %Activity{} = activity <- Activity.get_by_id(id), +         {:ok, activity} <- CommonAPI.remove_mute(user, activity) do +      try_render(conn, "show.json", activity: activity, for: user, as: :activity) +    end +  end + +  @doc "GET /api/v1/statuses/:id/card" +  @deprecated "https://github.com/tootsuite/mastodon/pull/11213" +  def card(%{assigns: %{user: user}} = conn, %{"id" => status_id}) do +    with %Activity{} = activity <- Activity.get_by_id(status_id), +         true <- Visibility.visible_for_user?(activity, user) do +      data = Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) +      render(conn, "card.json", data) +    else +      _ -> render_error(conn, :not_found, "Record not found") +    end +  end + +  @doc "GET /api/v1/statuses/:id/favourited_by" +  def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do +    with %Activity{} = activity <- Activity.get_by_id_with_object(id), +         {:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)}, +         %Object{data: %{"likes" => likes}} <- Object.normalize(activity) do +      users = +        User +        |> Ecto.Query.where([u], u.ap_id in ^likes) +        |> Repo.all() +        |> Enum.filter(&(not User.blocks?(user, &1))) + +      conn +      |> put_view(AccountView) +      |> render("accounts.json", for: user, users: users, as: :user) +    else +      {:visible, false} -> {:error, :not_found} +      _ -> json(conn, []) +    end +  end + +  @doc "GET /api/v1/statuses/:id/reblogged_by" +  def reblogged_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do +    with %Activity{} = activity <- Activity.get_by_id_with_object(id), +         {:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)}, +         %Object{data: %{"announcements" => announces}} <- Object.normalize(activity) do +      users = +        User +        |> Ecto.Query.where([u], u.ap_id in ^announces) +        |> Repo.all() +        |> Enum.filter(&(not User.blocks?(user, &1))) + +      conn +      |> put_view(AccountView) +      |> render("accounts.json", for: user, users: users, as: :user) +    else +      {:visible, false} -> {:error, :not_found} +      _ -> json(conn, []) +    end +  end + +  @doc "GET /api/v1/statuses/:id/context" +  def context(%{assigns: %{user: user}} = conn, %{"id" => id}) do +    with %Activity{} = activity <- Activity.get_by_id(id) do +      activities = +        ActivityPub.fetch_activities_for_context(activity.data["context"], %{ +          "blocking_user" => user, +          "user" => user, +          "exclude_id" => activity.id +        }) + +      render(conn, "context.json", activity: activity, activities: activities, user: user) +    end +  end +end diff --git a/lib/pleroma/web/mastodon_api/views/conversation_view.ex b/lib/pleroma/web/mastodon_api/views/conversation_view.ex index 40acc07b3..4aeb79d81 100644 --- a/lib/pleroma/web/mastodon_api/views/conversation_view.ex +++ b/lib/pleroma/web/mastodon_api/views/conversation_view.ex @@ -24,7 +24,7 @@ defmodule Pleroma.Web.MastodonAPI.ConversationView do      activity = Activity.get_by_id_with_object(last_activity_id) -    last_status = StatusView.render("status.json", %{activity: activity, for: user}) +    last_status = StatusView.render("show.json", %{activity: activity, for: user})      # Conversations return all users except the current user.      users = diff --git a/lib/pleroma/web/mastodon_api/views/notification_view.ex b/lib/pleroma/web/mastodon_api/views/notification_view.ex index ec8eadcaa..05110a192 100644 --- a/lib/pleroma/web/mastodon_api/views/notification_view.ex +++ b/lib/pleroma/web/mastodon_api/views/notification_view.ex @@ -39,19 +39,19 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do        "mention" ->          response          |> Map.merge(%{ -          status: StatusView.render("status.json", %{activity: activity, for: user}) +          status: StatusView.render("show.json", %{activity: activity, for: user})          })        "favourite" ->          response          |> Map.merge(%{ -          status: StatusView.render("status.json", %{activity: parent_activity, for: user}) +          status: StatusView.render("show.json", %{activity: parent_activity, for: user})          })        "reblog" ->          response          |> Map.merge(%{ -          status: StatusView.render("status.json", %{activity: parent_activity, for: user}) +          status: StatusView.render("show.json", %{activity: parent_activity, for: user})          })        "follow" -> diff --git a/lib/pleroma/web/mastodon_api/views/scheduled_activity_view.ex b/lib/pleroma/web/mastodon_api/views/scheduled_activity_view.ex index 0aae15ab9..fc042a276 100644 --- a/lib/pleroma/web/mastodon_api/views/scheduled_activity_view.ex +++ b/lib/pleroma/web/mastodon_api/views/scheduled_activity_view.ex @@ -7,11 +7,10 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityView do    alias Pleroma.ScheduledActivity    alias Pleroma.Web.CommonAPI -  alias Pleroma.Web.MastodonAPI.ScheduledActivityView    alias Pleroma.Web.MastodonAPI.StatusView    def render("index.json", %{scheduled_activities: scheduled_activities}) do -    render_many(scheduled_activities, ScheduledActivityView, "show.json") +    render_many(scheduled_activities, __MODULE__, "show.json")    end    def render("show.json", %{scheduled_activity: %ScheduledActivity{} = scheduled_activity}) do @@ -24,12 +23,8 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityView do    end    defp with_media_attachments(data, %{params: %{"media_attachments" => media_attachments}}) do -    try do -      attachments = render_many(media_attachments, StatusView, "attachment.json", as: :attachment) -      Map.put(data, :media_attachments, attachments) -    rescue -      _ -> data -    end +    attachments = render_many(media_attachments, StatusView, "attachment.json", as: :attachment) +    Map.put(data, :media_attachments, attachments)    end    defp with_media_attachments(data, _), do: data @@ -45,13 +40,9 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityView do        in_reply_to_id: params["in_reply_to_id"]      } -    data = -      if media_ids = params["media_ids"] do -        Map.put(data, :media_ids, media_ids) -      else -        data -      end - -    data +    case params["media_ids"] do +      nil -> data +      media_ids -> Map.put(data, :media_ids, media_ids) +    end    end  end diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index ef796cddd..2321d0de2 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -73,17 +73,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do    def render("index.json", opts) do      replied_to_activities = get_replied_to_activities(opts.activities) +    opts = Map.put(opts, :replied_to_activities, replied_to_activities) -    opts.activities -    |> safe_render_many( -      StatusView, -      "status.json", -      Map.put(opts, :replied_to_activities, replied_to_activities) -    ) +    safe_render_many(opts.activities, StatusView, "show.json", opts)    end    def render( -        "status.json", +        "show.json",          %{activity: %{data: %{"type" => "Announce", "object" => _object}} = activity} = opts        ) do      user = get_user(activity.data["actor"]) @@ -96,7 +92,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do        |> Activity.with_set_thread_muted_field(opts[:for])        |> Repo.one() -    reblogged = render("status.json", Map.put(opts, :activity, reblogged_activity)) +    reblogged = render("show.json", Map.put(opts, :activity, reblogged_activity))      favorited = opts[:for] && opts[:for].ap_id in (activity_object.data["likes"] || []) @@ -144,7 +140,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do      }    end -  def render("status.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do +  def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do      object = Object.normalize(activity)      user = get_user(activity.data["actor"]) @@ -303,7 +299,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do      }    end -  def render("status.json", _) do +  def render("show.json", _) do      nil    end @@ -343,9 +339,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do      }    end -  def render("card.json", _) do -    nil -  end +  def render("card.json", _), do: nil    def render("attachment.json", %{attachment: attachment}) do      [attachment_url | _] = attachment["url"] @@ -443,6 +437,20 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do      end    end +  def render("context.json", %{activity: activity, activities: activities, user: user}) do +    %{ancestors: ancestors, descendants: descendants} = +      activities +      |> Enum.reverse() +      |> Enum.group_by(fn %{id: id} -> if id < activity.id, do: :ancestors, else: :descendants end) +      |> Map.put_new(:ancestors, []) +      |> Map.put_new(:descendants, []) + +    %{ +      ancestors: render("index.json", for: user, activities: ancestors, as: :activity), +      descendants: render("index.json", for: user, activities: descendants, as: :activity) +    } +  end +    def get_reply_to(activity, %{replied_to_activities: replied_to_activities}) do      object = Object.normalize(activity) diff --git a/lib/pleroma/web/oauth/app.ex b/lib/pleroma/web/oauth/app.ex index ddcdb1871..cc3fb1ce5 100644 --- a/lib/pleroma/web/oauth/app.ex +++ b/lib/pleroma/web/oauth/app.ex @@ -5,6 +5,7 @@  defmodule Pleroma.Web.OAuth.App do    use Ecto.Schema    import Ecto.Changeset +  alias Pleroma.Repo    @type t :: %__MODULE__{} @@ -39,4 +40,29 @@ defmodule Pleroma.Web.OAuth.App do        changeset      end    end + +  @doc """ +  Gets app by attrs or create new  with attrs. +  And updates the scopes if need. +  """ +  @spec get_or_make(map(), list(String.t())) :: {:ok, App.t()} | {:error, Ecto.Changeset.t()} +  def get_or_make(attrs, scopes) do +    with %__MODULE__{} = app <- Repo.get_by(__MODULE__, attrs) do +      update_scopes(app, scopes) +    else +      _e -> +        %__MODULE__{} +        |> register_changeset(Map.put(attrs, :scopes, scopes)) +        |> Repo.insert() +    end +  end + +  defp update_scopes(%__MODULE__{} = app, []), do: {:ok, app} +  defp update_scopes(%__MODULE__{scopes: scopes} = app, scopes), do: {:ok, app} + +  defp update_scopes(%__MODULE__{} = app, scopes) do +    app +    |> change(%{scopes: scopes}) +    |> Repo.update() +  end  end diff --git a/lib/pleroma/web/pleroma_api/views/subscription_notification_view.ex b/lib/pleroma/web/pleroma_api/views/subscription_notification_view.ex index 0eccbcbb9..fc41a7389 100644 --- a/lib/pleroma/web/pleroma_api/views/subscription_notification_view.ex +++ b/lib/pleroma/web/pleroma_api/views/subscription_notification_view.ex @@ -36,19 +36,19 @@ defmodule Pleroma.Web.PleromaAPI.SubscriptionNotificationView do        "mention" ->          response          |> Map.merge(%{ -          status: StatusView.render("status.json", %{activity: activity, for: user}) +          status: StatusView.render("show.json", %{activity: activity, for: user})          })        "favourite" ->          response          |> Map.merge(%{ -          status: StatusView.render("status.json", %{activity: parent_activity, for: user}) +          status: StatusView.render("show.json", %{activity: parent_activity, for: user})          })        "reblog" ->          response          |> Map.merge(%{ -          status: StatusView.render("status.json", %{activity: parent_activity, for: user}) +          status: StatusView.render("show.json", %{activity: parent_activity, for: user})          })        "follow" -> diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 5b744e898..a025474e2 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -323,7 +323,7 @@ defmodule Pleroma.Web.Router do        get("/accounts/:id/lists", MastodonAPIController, :account_lists)        get("/accounts/:id/identity_proofs", MastodonAPIController, :empty_array) -      get("/follow_requests", MastodonAPIController, :follow_requests) +      get("/follow_requests", FollowRequestController, :index)        get("/blocks", MastodonAPIController, :blocks)        get("/mutes", MastodonAPIController, :mutes) @@ -339,16 +339,16 @@ defmodule Pleroma.Web.Router do        post("/notifications/dismiss", NotificationController, :dismiss)        delete("/notifications/destroy_multiple", NotificationController, :destroy_multiple) -      get("/scheduled_statuses", MastodonAPIController, :scheduled_statuses) -      get("/scheduled_statuses/:id", MastodonAPIController, :show_scheduled_status) +      get("/scheduled_statuses", ScheduledActivityController, :index) +      get("/scheduled_statuses/:id", ScheduledActivityController, :show)        get("/lists", ListController, :index)        get("/lists/:id", ListController, :show)        get("/lists/:id/accounts", ListController, :list_accounts) -      get("/domain_blocks", MastodonAPIController, :domain_blocks) +      get("/domain_blocks", DomainBlockController, :index) -      get("/filters", MastodonAPIController, :get_filters) +      get("/filters", FilterController, :index)        get("/suggestions", MastodonAPIController, :suggestions) @@ -363,22 +363,22 @@ defmodule Pleroma.Web.Router do        patch("/accounts/update_credentials", MastodonAPIController, :update_credentials) -      post("/statuses", MastodonAPIController, :post_status) -      delete("/statuses/:id", MastodonAPIController, :delete_status) +      post("/statuses", StatusController, :create) +      delete("/statuses/:id", StatusController, :delete) -      post("/statuses/:id/reblog", MastodonAPIController, :reblog_status) -      post("/statuses/:id/unreblog", MastodonAPIController, :unreblog_status) -      post("/statuses/:id/favourite", MastodonAPIController, :fav_status) -      post("/statuses/:id/unfavourite", MastodonAPIController, :unfav_status) -      post("/statuses/:id/pin", MastodonAPIController, :pin_status) -      post("/statuses/:id/unpin", MastodonAPIController, :unpin_status) -      post("/statuses/:id/bookmark", MastodonAPIController, :bookmark_status) -      post("/statuses/:id/unbookmark", MastodonAPIController, :unbookmark_status) -      post("/statuses/:id/mute", MastodonAPIController, :mute_conversation) -      post("/statuses/:id/unmute", MastodonAPIController, :unmute_conversation) +      post("/statuses/:id/reblog", StatusController, :reblog) +      post("/statuses/:id/unreblog", StatusController, :unreblog) +      post("/statuses/:id/favourite", StatusController, :favourite) +      post("/statuses/:id/unfavourite", StatusController, :unfavourite) +      post("/statuses/:id/pin", StatusController, :pin) +      post("/statuses/:id/unpin", StatusController, :unpin) +      post("/statuses/:id/bookmark", StatusController, :bookmark) +      post("/statuses/:id/unbookmark", StatusController, :unbookmark) +      post("/statuses/:id/mute", StatusController, :mute_conversation) +      post("/statuses/:id/unmute", StatusController, :unmute_conversation) -      put("/scheduled_statuses/:id", MastodonAPIController, :update_scheduled_status) -      delete("/scheduled_statuses/:id", MastodonAPIController, :delete_scheduled_status) +      put("/scheduled_statuses/:id", ScheduledActivityController, :update) +      delete("/scheduled_statuses/:id", ScheduledActivityController, :delete)        post("/polls/:id/votes", MastodonAPIController, :poll_vote) @@ -392,10 +392,10 @@ defmodule Pleroma.Web.Router do        post("/lists/:id/accounts", ListController, :add_to_list)        delete("/lists/:id/accounts", ListController, :remove_from_list) -      post("/filters", MastodonAPIController, :create_filter) -      get("/filters/:id", MastodonAPIController, :get_filter) -      put("/filters/:id", MastodonAPIController, :update_filter) -      delete("/filters/:id", MastodonAPIController, :delete_filter) +      post("/filters", FilterController, :create) +      get("/filters/:id", FilterController, :show) +      put("/filters/:id", FilterController, :update) +      delete("/filters/:id", FilterController, :delete)        patch("/pleroma/accounts/update_avatar", MastodonAPIController, :update_avatar)        patch("/pleroma/accounts/update_banner", MastodonAPIController, :update_banner) @@ -419,11 +419,11 @@ defmodule Pleroma.Web.Router do        post("/accounts/:id/mute", MastodonAPIController, :mute)        post("/accounts/:id/unmute", MastodonAPIController, :unmute) -      post("/follow_requests/:id/authorize", MastodonAPIController, :authorize_follow_request) -      post("/follow_requests/:id/reject", MastodonAPIController, :reject_follow_request) +      post("/follow_requests/:id/authorize", FollowRequestController, :authorize) +      post("/follow_requests/:id/reject", FollowRequestController, :reject) -      post("/domain_blocks", MastodonAPIController, :block_domain) -      delete("/domain_blocks", MastodonAPIController, :unblock_domain) +      post("/domain_blocks", DomainBlockController, :create) +      delete("/domain_blocks", DomainBlockController, :delete)        post("/pleroma/accounts/:id/subscribe", MastodonAPIController, :subscribe)        post("/pleroma/accounts/:id/unsubscribe", MastodonAPIController, :unsubscribe) @@ -456,10 +456,10 @@ defmodule Pleroma.Web.Router do      get("/apps/verify_credentials", MastodonAPIController, :verify_app_credentials)      get("/custom_emojis", MastodonAPIController, :custom_emojis) -    get("/statuses/:id/card", MastodonAPIController, :status_card) +    get("/statuses/:id/card", StatusController, :card) -    get("/statuses/:id/favourited_by", MastodonAPIController, :favourited_by) -    get("/statuses/:id/reblogged_by", MastodonAPIController, :reblogged_by) +    get("/statuses/:id/favourited_by", StatusController, :favourited_by) +    get("/statuses/:id/reblogged_by", StatusController, :reblogged_by)      get("/trends", MastodonAPIController, :empty_array) @@ -478,9 +478,9 @@ defmodule Pleroma.Web.Router do        get("/timelines/tag/:tag", TimelineController, :hashtag)        get("/timelines/list/:list_id", TimelineController, :list) -      get("/statuses", MastodonAPIController, :get_statuses) -      get("/statuses/:id", MastodonAPIController, :get_status) -      get("/statuses/:id/context", MastodonAPIController, :get_context) +      get("/statuses", StatusController, :index) +      get("/statuses/:id", StatusController, :show) +      get("/statuses/:id/context", StatusController, :context)        get("/polls/:id", MastodonAPIController, :get_poll) diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index 8eda762c7..bfd838902 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -29,7 +29,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do      captcha_enabled = Pleroma.Config.get([Pleroma.Captcha, :enabled])      # true if captcha is disabled or enabled and valid, false otherwise      captcha_ok = -      if !captcha_enabled do +      if not captcha_enabled do          :ok        else          Pleroma.Captcha.validate( diff --git a/lib/pleroma/web/views/streamer_view.ex b/lib/pleroma/web/views/streamer_view.ex index b13030fa0..a9f14d09a 100644 --- a/lib/pleroma/web/views/streamer_view.ex +++ b/lib/pleroma/web/views/streamer_view.ex @@ -16,7 +16,7 @@ defmodule Pleroma.Web.StreamerView do        event: "update",        payload:          Pleroma.Web.MastodonAPI.StatusView.render( -          "status.json", +          "show.json",            activity: activity,            for: user          ) @@ -43,7 +43,7 @@ defmodule Pleroma.Web.StreamerView do        event: "update",        payload:          Pleroma.Web.MastodonAPI.StatusView.render( -          "status.json", +          "show.json",            activity: activity          )          |> Jason.encode!() @@ -159,6 +159,9 @@ defmodule Pleroma.Mixfile do        {:plug_static_index_html, "~> 1.0.0"},        {:excoveralls, "~> 0.11.1", only: :test},        {:flake_id, "~> 0.1.0"}, +      {:remote_ip, +       git: "https://git.pleroma.social/pleroma/remote_ip.git", +       ref: "825dc00aaba5a1b7c4202a532b696b595dd3bcb3"},        {:mox, "~> 0.5", only: :test}      ] ++ oauth_deps()    end @@ -48,6 +48,7 @@    "http_signatures": {:git, "https://git.pleroma.social/pleroma/http_signatures.git", "293d77bb6f4a67ac8bde1428735c3b42f22cbb30", [ref: "293d77bb6f4a67ac8bde1428735c3b42f22cbb30"]},    "httpoison": {:hex, :httpoison, "1.2.0", "2702ed3da5fd7a8130fc34b11965c8cfa21ade2f232c00b42d96d4967c39a3a3", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},    "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, +  "inet_cidr": {:hex, :inet_cidr, "1.0.4", "a05744ab7c221ca8e395c926c3919a821eb512e8f36547c062f62c4ca0cf3d6e", [:mix], [], "hexpm"},    "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},    "joken": {:hex, :joken, "2.0.1", "ec9ab31bf660f343380da033b3316855197c8d4c6ef597fa3fcb451b326beb14", [:mix], [{:jose, "~> 1.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm"},    "jose": {:hex, :jose, "1.9.0", "4167c5f6d06ffaebffd15cdb8da61a108445ef5e85ab8f5a7ad926fdf3ada154", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"}, @@ -87,6 +88,7 @@    "quantum": {:hex, :quantum, "2.3.4", "72a0e8855e2adc101459eac8454787cb74ab4169de6ca50f670e72142d4960e9", [:mix], [{:calendar, "~> 0.17", [hex: :calendar, repo: "hexpm", optional: true]}, {:crontab, "~> 1.1", [hex: :crontab, repo: "hexpm", optional: false]}, {:gen_stage, "~> 0.12", [hex: :gen_stage, repo: "hexpm", optional: false]}, {:swarm, "~> 3.3", [hex: :swarm, repo: "hexpm", optional: false]}, {:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: true]}], "hexpm"},    "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},    "recon": {:git, "https://github.com/ferd/recon.git", "75d70c7c08926d2f24f1ee6de14ee50fe8a52763", [tag: "2.4.0"]}, +  "remote_ip": {:git, "https://git.pleroma.social/pleroma/remote_ip.git", "825dc00aaba5a1b7c4202a532b696b595dd3bcb3", [ref: "825dc00aaba5a1b7c4202a532b696b595dd3bcb3"]},    "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm"},    "swarm": {:hex, :swarm, "3.4.0", "64f8b30055d74640d2186c66354b33b999438692a91be275bb89cdc7e401f448", [:mix], [{:gen_state_machine, "~> 2.0", [hex: :gen_state_machine, repo: "hexpm", optional: false]}, {:libring, "~> 1.0", [hex: :libring, repo: "hexpm", optional: false]}], "hexpm"},    "sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm"}, diff --git a/test/integration/mastodon_websocket_test.exs b/test/integration/mastodon_websocket_test.exs index ed7ce8fe0..63fce07bb 100644 --- a/test/integration/mastodon_websocket_test.exs +++ b/test/integration/mastodon_websocket_test.exs @@ -68,7 +68,7 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do      assert {:ok, json} = Jason.decode(json["payload"])      view_json = -      Pleroma.Web.MastodonAPI.StatusView.render("status.json", activity: activity, for: nil) +      Pleroma.Web.MastodonAPI.StatusView.render("show.json", activity: activity, for: nil)        |> Jason.encode!()        |> Jason.decode!() diff --git a/test/plugs/remote_ip_test.exs b/test/plugs/remote_ip_test.exs new file mode 100644 index 000000000..d120c588b --- /dev/null +++ b/test/plugs/remote_ip_test.exs @@ -0,0 +1,72 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Plugs.RemoteIpTest do +  use ExUnit.Case, async: true +  use Plug.Test + +  alias Pleroma.Plugs.RemoteIp + +  test "disabled" do +    Pleroma.Config.put(RemoteIp, enabled: false) + +    %{remote_ip: remote_ip} = conn(:get, "/") + +    conn = +      conn(:get, "/") +      |> put_req_header("x-forwarded-for", "1.1.1.1") +      |> RemoteIp.call(nil) + +    assert conn.remote_ip == remote_ip +  end + +  test "enabled" do +    Pleroma.Config.put(RemoteIp, enabled: true) + +    conn = +      conn(:get, "/") +      |> put_req_header("x-forwarded-for", "1.1.1.1") +      |> RemoteIp.call(nil) + +    assert conn.remote_ip == {1, 1, 1, 1} +  end + +  test "custom headers" do +    Pleroma.Config.put(RemoteIp, enabled: true, headers: ["cf-connecting-ip"]) + +    conn = +      conn(:get, "/") +      |> put_req_header("x-forwarded-for", "1.1.1.1") +      |> RemoteIp.call(nil) + +    refute conn.remote_ip == {1, 1, 1, 1} + +    conn = +      conn(:get, "/") +      |> put_req_header("cf-connecting-ip", "1.1.1.1") +      |> RemoteIp.call(nil) + +    assert conn.remote_ip == {1, 1, 1, 1} +  end + +  test "custom proxies" do +    Pleroma.Config.put(RemoteIp, enabled: true) + +    conn = +      conn(:get, "/") +      |> put_req_header("x-forwarded-for", "173.245.48.1, 1.1.1.1, 173.245.48.2") +      |> RemoteIp.call(nil) + +    refute conn.remote_ip == {1, 1, 1, 1} + +    Pleroma.Config.put([RemoteIp, :proxies], ["173.245.48.0/20"]) + +    conn = +      conn(:get, "/") +      |> put_req_header("x-forwarded-for", "173.245.48.1, 1.1.1.1, 173.245.48.2") +      |> RemoteIp.call(nil) + +    assert conn.remote_ip == {1, 1, 1, 1} +  end +end diff --git a/test/web/admin_api/views/report_view_test.exs b/test/web/admin_api/views/report_view_test.exs index 40df01101..35b6947a0 100644 --- a/test/web/admin_api/views/report_view_test.exs +++ b/test/web/admin_api/views/report_view_test.exs @@ -61,7 +61,7 @@ defmodule Pleroma.Web.AdminAPI.ReportViewTest do            AccountView.render("account.json", %{user: other_user}),            Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: other_user})          ), -      statuses: [StatusView.render("status.json", %{activity: activity})], +      statuses: [StatusView.render("show.json", %{activity: activity})],        state: "open",        id: report_activity.id      } diff --git a/test/web/mastodon_api/controllers/domain_block_controller_test.exs b/test/web/mastodon_api/controllers/domain_block_controller_test.exs new file mode 100644 index 000000000..3c3558385 --- /dev/null +++ b/test/web/mastodon_api/controllers/domain_block_controller_test.exs @@ -0,0 +1,51 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.DomainBlockControllerTest do +  use Pleroma.Web.ConnCase, async: true + +  alias Pleroma.User + +  import Pleroma.Factory + +  test "blocking / unblocking a domain", %{conn: conn} do +    user = insert(:user) +    other_user = insert(:user, %{ap_id: "https://dogwhistle.zone/@pundit"}) + +    conn = +      conn +      |> assign(:user, user) +      |> post("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"}) + +    assert %{} = json_response(conn, 200) +    user = User.get_cached_by_ap_id(user.ap_id) +    assert User.blocks?(user, other_user) + +    conn = +      build_conn() +      |> assign(:user, user) +      |> delete("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"}) + +    assert %{} = json_response(conn, 200) +    user = User.get_cached_by_ap_id(user.ap_id) +    refute User.blocks?(user, other_user) +  end + +  test "getting a list of domain blocks", %{conn: conn} do +    user = insert(:user) + +    {:ok, user} = User.block_domain(user, "bad.site") +    {:ok, user} = User.block_domain(user, "even.worse.site") + +    conn = +      conn +      |> assign(:user, user) +      |> get("/api/v1/domain_blocks") + +    domain_blocks = json_response(conn, 200) + +    assert "bad.site" in domain_blocks +    assert "even.worse.site" in domain_blocks +  end +end diff --git a/test/web/mastodon_api/controllers/filter_controller_test.exs b/test/web/mastodon_api/controllers/filter_controller_test.exs new file mode 100644 index 000000000..5d5b56c8e --- /dev/null +++ b/test/web/mastodon_api/controllers/filter_controller_test.exs @@ -0,0 +1,137 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.FilterControllerTest do +  use Pleroma.Web.ConnCase, async: true + +  alias Pleroma.Web.MastodonAPI.FilterView + +  import Pleroma.Factory + +  test "creating a filter", %{conn: conn} do +    user = insert(:user) + +    filter = %Pleroma.Filter{ +      phrase: "knights", +      context: ["home"] +    } + +    conn = +      conn +      |> assign(:user, user) +      |> post("/api/v1/filters", %{"phrase" => filter.phrase, context: filter.context}) + +    assert response = json_response(conn, 200) +    assert response["phrase"] == filter.phrase +    assert response["context"] == filter.context +    assert response["irreversible"] == false +    assert response["id"] != nil +    assert response["id"] != "" +  end + +  test "fetching a list of filters", %{conn: conn} do +    user = insert(:user) + +    query_one = %Pleroma.Filter{ +      user_id: user.id, +      filter_id: 1, +      phrase: "knights", +      context: ["home"] +    } + +    query_two = %Pleroma.Filter{ +      user_id: user.id, +      filter_id: 2, +      phrase: "who", +      context: ["home"] +    } + +    {:ok, filter_one} = Pleroma.Filter.create(query_one) +    {:ok, filter_two} = Pleroma.Filter.create(query_two) + +    response = +      conn +      |> assign(:user, user) +      |> get("/api/v1/filters") +      |> json_response(200) + +    assert response == +             render_json( +               FilterView, +               "filters.json", +               filters: [filter_two, filter_one] +             ) +  end + +  test "get a filter", %{conn: conn} do +    user = insert(:user) + +    query = %Pleroma.Filter{ +      user_id: user.id, +      filter_id: 2, +      phrase: "knight", +      context: ["home"] +    } + +    {:ok, filter} = Pleroma.Filter.create(query) + +    conn = +      conn +      |> assign(:user, user) +      |> get("/api/v1/filters/#{filter.filter_id}") + +    assert _response = json_response(conn, 200) +  end + +  test "update a filter", %{conn: conn} do +    user = insert(:user) + +    query = %Pleroma.Filter{ +      user_id: user.id, +      filter_id: 2, +      phrase: "knight", +      context: ["home"] +    } + +    {:ok, _filter} = Pleroma.Filter.create(query) + +    new = %Pleroma.Filter{ +      phrase: "nii", +      context: ["home"] +    } + +    conn = +      conn +      |> assign(:user, user) +      |> put("/api/v1/filters/#{query.filter_id}", %{ +        phrase: new.phrase, +        context: new.context +      }) + +    assert response = json_response(conn, 200) +    assert response["phrase"] == new.phrase +    assert response["context"] == new.context +  end + +  test "delete a filter", %{conn: conn} do +    user = insert(:user) + +    query = %Pleroma.Filter{ +      user_id: user.id, +      filter_id: 2, +      phrase: "knight", +      context: ["home"] +    } + +    {:ok, filter} = Pleroma.Filter.create(query) + +    conn = +      conn +      |> assign(:user, user) +      |> delete("/api/v1/filters/#{filter.filter_id}") + +    assert response = json_response(conn, 200) +    assert response == %{} +  end +end diff --git a/test/web/mastodon_api/controllers/follow_request_controller_test.exs b/test/web/mastodon_api/controllers/follow_request_controller_test.exs new file mode 100644 index 000000000..4bf292df5 --- /dev/null +++ b/test/web/mastodon_api/controllers/follow_request_controller_test.exs @@ -0,0 +1,81 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do +  use Pleroma.Web.ConnCase + +  alias Pleroma.User +  alias Pleroma.Web.ActivityPub.ActivityPub + +  import Pleroma.Factory + +  describe "locked accounts" do +    test "/api/v1/follow_requests works" do +      user = insert(:user, %{info: %User.Info{locked: true}}) +      other_user = insert(:user) + +      {:ok, _activity} = ActivityPub.follow(other_user, user) + +      user = User.get_cached_by_id(user.id) +      other_user = User.get_cached_by_id(other_user.id) + +      assert User.following?(other_user, user) == false + +      conn = +        build_conn() +        |> assign(:user, user) +        |> get("/api/v1/follow_requests") + +      assert [relationship] = json_response(conn, 200) +      assert to_string(other_user.id) == relationship["id"] +    end + +    test "/api/v1/follow_requests/:id/authorize works" do +      user = insert(:user, %{info: %User.Info{locked: true}}) +      other_user = insert(:user) + +      {:ok, _activity} = ActivityPub.follow(other_user, user) + +      user = User.get_cached_by_id(user.id) +      other_user = User.get_cached_by_id(other_user.id) + +      assert User.following?(other_user, user) == false + +      conn = +        build_conn() +        |> assign(:user, user) +        |> post("/api/v1/follow_requests/#{other_user.id}/authorize") + +      assert relationship = json_response(conn, 200) +      assert to_string(other_user.id) == relationship["id"] + +      user = User.get_cached_by_id(user.id) +      other_user = User.get_cached_by_id(other_user.id) + +      assert User.following?(other_user, user) == true +    end + +    test "/api/v1/follow_requests/:id/reject works" do +      user = insert(:user, %{info: %User.Info{locked: true}}) +      other_user = insert(:user) + +      {:ok, _activity} = ActivityPub.follow(other_user, user) + +      user = User.get_cached_by_id(user.id) + +      conn = +        build_conn() +        |> assign(:user, user) +        |> post("/api/v1/follow_requests/#{other_user.id}/reject") + +      assert relationship = json_response(conn, 200) +      assert to_string(other_user.id) == relationship["id"] + +      user = User.get_cached_by_id(user.id) +      other_user = User.get_cached_by_id(other_user.id) + +      assert User.following?(other_user, user) == false +    end +  end +end diff --git a/test/web/mastodon_api/controllers/scheduled_activity_controller_test.exs b/test/web/mastodon_api/controllers/scheduled_activity_controller_test.exs new file mode 100644 index 000000000..9ad6a4fa7 --- /dev/null +++ b/test/web/mastodon_api/controllers/scheduled_activity_controller_test.exs @@ -0,0 +1,113 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.ScheduledActivityControllerTest do +  use Pleroma.Web.ConnCase, async: true + +  alias Pleroma.Repo +  alias Pleroma.ScheduledActivity + +  import Pleroma.Factory + +  test "shows scheduled activities", %{conn: conn} do +    user = insert(:user) +    scheduled_activity_id1 = insert(:scheduled_activity, user: user).id |> to_string() +    scheduled_activity_id2 = insert(:scheduled_activity, user: user).id |> to_string() +    scheduled_activity_id3 = insert(:scheduled_activity, user: user).id |> to_string() +    scheduled_activity_id4 = insert(:scheduled_activity, user: user).id |> to_string() + +    conn = +      conn +      |> assign(:user, user) + +    # min_id +    conn_res = +      conn +      |> get("/api/v1/scheduled_statuses?limit=2&min_id=#{scheduled_activity_id1}") + +    result = json_response(conn_res, 200) +    assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result + +    # since_id +    conn_res = +      conn +      |> get("/api/v1/scheduled_statuses?limit=2&since_id=#{scheduled_activity_id1}") + +    result = json_response(conn_res, 200) +    assert [%{"id" => ^scheduled_activity_id4}, %{"id" => ^scheduled_activity_id3}] = result + +    # max_id +    conn_res = +      conn +      |> get("/api/v1/scheduled_statuses?limit=2&max_id=#{scheduled_activity_id4}") + +    result = json_response(conn_res, 200) +    assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result +  end + +  test "shows a scheduled activity", %{conn: conn} do +    user = insert(:user) +    scheduled_activity = insert(:scheduled_activity, user: user) + +    res_conn = +      conn +      |> assign(:user, user) +      |> get("/api/v1/scheduled_statuses/#{scheduled_activity.id}") + +    assert %{"id" => scheduled_activity_id} = json_response(res_conn, 200) +    assert scheduled_activity_id == scheduled_activity.id |> to_string() + +    res_conn = +      conn +      |> assign(:user, user) +      |> get("/api/v1/scheduled_statuses/404") + +    assert %{"error" => "Record not found"} = json_response(res_conn, 404) +  end + +  test "updates a scheduled activity", %{conn: conn} do +    user = insert(:user) +    scheduled_activity = insert(:scheduled_activity, user: user) + +    new_scheduled_at = +      NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond) + +    res_conn = +      conn +      |> assign(:user, user) +      |> put("/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{ +        scheduled_at: new_scheduled_at +      }) + +    assert %{"scheduled_at" => expected_scheduled_at} = json_response(res_conn, 200) +    assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(new_scheduled_at) + +    res_conn = +      conn +      |> assign(:user, user) +      |> put("/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at}) + +    assert %{"error" => "Record not found"} = json_response(res_conn, 404) +  end + +  test "deletes a scheduled activity", %{conn: conn} do +    user = insert(:user) +    scheduled_activity = insert(:scheduled_activity, user: user) + +    res_conn = +      conn +      |> assign(:user, user) +      |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}") + +    assert %{} = json_response(res_conn, 200) +    assert nil == Repo.get(ScheduledActivity, scheduled_activity.id) + +    res_conn = +      conn +      |> assign(:user, user) +      |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}") + +    assert %{"error" => "Record not found"} = json_response(res_conn, 404) +  end +end diff --git a/test/web/mastodon_api/controllers/status_controller_test.exs b/test/web/mastodon_api/controllers/status_controller_test.exs new file mode 100644 index 000000000..b194feae6 --- /dev/null +++ b/test/web/mastodon_api/controllers/status_controller_test.exs @@ -0,0 +1,1210 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do +  use Pleroma.Web.ConnCase + +  alias Pleroma.Activity +  alias Pleroma.ActivityExpiration +  alias Pleroma.Config +  alias Pleroma.Object +  alias Pleroma.Repo +  alias Pleroma.ScheduledActivity +  alias Pleroma.User +  alias Pleroma.Web.ActivityPub.ActivityPub +  alias Pleroma.Web.CommonAPI + +  import Pleroma.Factory + +  describe "posting statuses" do +    setup do +      user = insert(:user) + +      conn = +        build_conn() +        |> assign(:user, user) + +      [conn: conn] +    end + +    test "posting a status", %{conn: conn} do +      idempotency_key = "Pikachu rocks!" + +      conn_one = +        conn +        |> put_req_header("idempotency-key", idempotency_key) +        |> post("/api/v1/statuses", %{ +          "status" => "cofe", +          "spoiler_text" => "2hu", +          "sensitive" => "false" +        }) + +      {:ok, ttl} = Cachex.ttl(:idempotency_cache, idempotency_key) +      # Six hours +      assert ttl > :timer.seconds(6 * 60 * 60 - 1) + +      assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} = +               json_response(conn_one, 200) + +      assert Activity.get_by_id(id) + +      conn_two = +        conn +        |> put_req_header("idempotency-key", idempotency_key) +        |> post("/api/v1/statuses", %{ +          "status" => "cofe", +          "spoiler_text" => "2hu", +          "sensitive" => "false" +        }) + +      assert %{"id" => second_id} = json_response(conn_two, 200) +      assert id == second_id + +      conn_three = +        conn +        |> post("/api/v1/statuses", %{ +          "status" => "cofe", +          "spoiler_text" => "2hu", +          "sensitive" => "false" +        }) + +      assert %{"id" => third_id} = json_response(conn_three, 200) +      refute id == third_id + +      # An activity that will expire: +      # 2 hours +      expires_in = 120 * 60 + +      conn_four = +        conn +        |> post("api/v1/statuses", %{ +          "status" => "oolong", +          "expires_in" => expires_in +        }) + +      assert fourth_response = %{"id" => fourth_id} = json_response(conn_four, 200) +      assert activity = Activity.get_by_id(fourth_id) +      assert expiration = ActivityExpiration.get_by_activity_id(fourth_id) + +      estimated_expires_at = +        NaiveDateTime.utc_now() +        |> NaiveDateTime.add(expires_in) +        |> NaiveDateTime.truncate(:second) + +      # This assert will fail if the test takes longer than a minute. I sure hope it never does: +      assert abs(NaiveDateTime.diff(expiration.scheduled_at, estimated_expires_at, :second)) < 60 + +      assert fourth_response["pleroma"]["expires_at"] == +               NaiveDateTime.to_iso8601(expiration.scheduled_at) +    end + +    test "posting an undefined status with an attachment", %{conn: conn} do +      user = insert(:user) + +      file = %Plug.Upload{ +        content_type: "image/jpg", +        path: Path.absname("test/fixtures/image.jpg"), +        filename: "an_image.jpg" +      } + +      {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id) + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses", %{ +          "media_ids" => [to_string(upload.id)] +        }) + +      assert json_response(conn, 200) +    end + +    test "replying to a status", %{conn: conn} do +      user = insert(:user) +      {:ok, replied_to} = CommonAPI.post(user, %{"status" => "cofe"}) + +      conn = +        conn +        |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id}) + +      assert %{"content" => "xD", "id" => id} = json_response(conn, 200) + +      activity = Activity.get_by_id(id) + +      assert activity.data["context"] == replied_to.data["context"] +      assert Activity.get_in_reply_to_activity(activity).id == replied_to.id +    end + +    test "replying to a direct message with visibility other than direct", %{conn: conn} do +      user = insert(:user) +      {:ok, replied_to} = CommonAPI.post(user, %{"status" => "suya..", "visibility" => "direct"}) + +      Enum.each(["public", "private", "unlisted"], fn visibility -> +        conn = +          conn +          |> post("/api/v1/statuses", %{ +            "status" => "@#{user.nickname} hey", +            "in_reply_to_id" => replied_to.id, +            "visibility" => visibility +          }) + +        assert json_response(conn, 422) == %{"error" => "The message visibility must be direct"} +      end) +    end + +    test "posting a status with an invalid in_reply_to_id", %{conn: conn} do +      conn = +        conn +        |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => ""}) + +      assert %{"content" => "xD", "id" => id} = json_response(conn, 200) +      assert Activity.get_by_id(id) +    end + +    test "posting a sensitive status", %{conn: conn} do +      conn = +        conn +        |> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true}) + +      assert %{"content" => "cofe", "id" => id, "sensitive" => true} = json_response(conn, 200) +      assert Activity.get_by_id(id) +    end + +    test "posting a fake status", %{conn: conn} do +      real_conn = +        conn +        |> post("/api/v1/statuses", %{ +          "status" => +            "\"Tenshi Eating a Corndog\" is a much discussed concept on /jp/. The significance of it is disputed, so I will focus on one core concept: the symbolism behind it" +        }) + +      real_status = json_response(real_conn, 200) + +      assert real_status +      assert Object.get_by_ap_id(real_status["uri"]) + +      real_status = +        real_status +        |> Map.put("id", nil) +        |> Map.put("url", nil) +        |> Map.put("uri", nil) +        |> Map.put("created_at", nil) +        |> Kernel.put_in(["pleroma", "conversation_id"], nil) + +      fake_conn = +        conn +        |> post("/api/v1/statuses", %{ +          "status" => +            "\"Tenshi Eating a Corndog\" is a much discussed concept on /jp/. The significance of it is disputed, so I will focus on one core concept: the symbolism behind it", +          "preview" => true +        }) + +      fake_status = json_response(fake_conn, 200) + +      assert fake_status +      refute Object.get_by_ap_id(fake_status["uri"]) + +      fake_status = +        fake_status +        |> Map.put("id", nil) +        |> Map.put("url", nil) +        |> Map.put("uri", nil) +        |> Map.put("created_at", nil) +        |> Kernel.put_in(["pleroma", "conversation_id"], nil) + +      assert real_status == fake_status +    end + +    test "posting a status with OGP link preview", %{conn: conn} do +      Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end) +      Config.put([:rich_media, :enabled], true) + +      conn = +        conn +        |> post("/api/v1/statuses", %{ +          "status" => "https://example.com/ogp" +        }) + +      assert %{"id" => id, "card" => %{"title" => "The Rock"}} = json_response(conn, 200) +      assert Activity.get_by_id(id) +    end + +    test "posting a direct status", %{conn: conn} do +      user2 = insert(:user) +      content = "direct cofe @#{user2.nickname}" + +      conn = +        conn +        |> post("api/v1/statuses", %{"status" => content, "visibility" => "direct"}) + +      assert %{"id" => id} = response = json_response(conn, 200) +      assert response["visibility"] == "direct" +      assert response["pleroma"]["direct_conversation_id"] +      assert activity = Activity.get_by_id(id) +      assert activity.recipients == [user2.ap_id, conn.assigns[:user].ap_id] +      assert activity.data["to"] == [user2.ap_id] +      assert activity.data["cc"] == [] +    end +  end + +  describe "posting scheduled statuses" do +    test "creates a scheduled activity", %{conn: conn} do +      user = insert(:user) +      scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond) + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses", %{ +          "status" => "scheduled", +          "scheduled_at" => scheduled_at +        }) + +      assert %{"scheduled_at" => expected_scheduled_at} = json_response(conn, 200) +      assert expected_scheduled_at == CommonAPI.Utils.to_masto_date(scheduled_at) +      assert [] == Repo.all(Activity) +    end + +    test "creates a scheduled activity with a media attachment", %{conn: conn} do +      user = insert(:user) +      scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond) + +      file = %Plug.Upload{ +        content_type: "image/jpg", +        path: Path.absname("test/fixtures/image.jpg"), +        filename: "an_image.jpg" +      } + +      {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id) + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses", %{ +          "media_ids" => [to_string(upload.id)], +          "status" => "scheduled", +          "scheduled_at" => scheduled_at +        }) + +      assert %{"media_attachments" => [media_attachment]} = json_response(conn, 200) +      assert %{"type" => "image"} = media_attachment +    end + +    test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now", +         %{conn: conn} do +      user = insert(:user) + +      scheduled_at = +        NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond) + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses", %{ +          "status" => "not scheduled", +          "scheduled_at" => scheduled_at +        }) + +      assert %{"content" => "not scheduled"} = json_response(conn, 200) +      assert [] == Repo.all(ScheduledActivity) +    end + +    test "returns error when daily user limit is exceeded", %{conn: conn} do +      user = insert(:user) + +      today = +        NaiveDateTime.utc_now() +        |> NaiveDateTime.add(:timer.minutes(6), :millisecond) +        |> NaiveDateTime.to_iso8601() + +      attrs = %{params: %{}, scheduled_at: today} +      {:ok, _} = ScheduledActivity.create(user, attrs) +      {:ok, _} = ScheduledActivity.create(user, attrs) + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today}) + +      assert %{"error" => "daily limit exceeded"} == json_response(conn, 422) +    end + +    test "returns error when total user limit is exceeded", %{conn: conn} do +      user = insert(:user) + +      today = +        NaiveDateTime.utc_now() +        |> NaiveDateTime.add(:timer.minutes(6), :millisecond) +        |> NaiveDateTime.to_iso8601() + +      tomorrow = +        NaiveDateTime.utc_now() +        |> NaiveDateTime.add(:timer.hours(36), :millisecond) +        |> NaiveDateTime.to_iso8601() + +      attrs = %{params: %{}, scheduled_at: today} +      {:ok, _} = ScheduledActivity.create(user, attrs) +      {:ok, _} = ScheduledActivity.create(user, attrs) +      {:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow}) + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow}) + +      assert %{"error" => "total limit exceeded"} == json_response(conn, 422) +    end +  end + +  describe "posting polls" do +    test "posting a poll", %{conn: conn} do +      user = insert(:user) +      time = NaiveDateTime.utc_now() + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses", %{ +          "status" => "Who is the #bestgrill?", +          "poll" => %{"options" => ["Rei", "Asuka", "Misato"], "expires_in" => 420} +        }) + +      response = json_response(conn, 200) + +      assert Enum.all?(response["poll"]["options"], fn %{"title" => title} -> +               title in ["Rei", "Asuka", "Misato"] +             end) + +      assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430 +      refute response["poll"]["expred"] +    end + +    test "option limit is enforced", %{conn: conn} do +      user = insert(:user) +      limit = Config.get([:instance, :poll_limits, :max_options]) + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses", %{ +          "status" => "desu~", +          "poll" => %{"options" => Enum.map(0..limit, fn _ -> "desu" end), "expires_in" => 1} +        }) + +      %{"error" => error} = json_response(conn, 422) +      assert error == "Poll can't contain more than #{limit} options" +    end + +    test "option character limit is enforced", %{conn: conn} do +      user = insert(:user) +      limit = Config.get([:instance, :poll_limits, :max_option_chars]) + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses", %{ +          "status" => "...", +          "poll" => %{ +            "options" => [Enum.reduce(0..limit, "", fn _, acc -> acc <> "." end)], +            "expires_in" => 1 +          } +        }) + +      %{"error" => error} = json_response(conn, 422) +      assert error == "Poll options cannot be longer than #{limit} characters each" +    end + +    test "minimal date limit is enforced", %{conn: conn} do +      user = insert(:user) +      limit = Config.get([:instance, :poll_limits, :min_expiration]) + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses", %{ +          "status" => "imagine arbitrary limits", +          "poll" => %{ +            "options" => ["this post was made by pleroma gang"], +            "expires_in" => limit - 1 +          } +        }) + +      %{"error" => error} = json_response(conn, 422) +      assert error == "Expiration date is too soon" +    end + +    test "maximum date limit is enforced", %{conn: conn} do +      user = insert(:user) +      limit = Config.get([:instance, :poll_limits, :max_expiration]) + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses", %{ +          "status" => "imagine arbitrary limits", +          "poll" => %{ +            "options" => ["this post was made by pleroma gang"], +            "expires_in" => limit + 1 +          } +        }) + +      %{"error" => error} = json_response(conn, 422) +      assert error == "Expiration date is too far in the future" +    end +  end + +  test "get a status", %{conn: conn} do +    activity = insert(:note_activity) + +    conn = +      conn +      |> get("/api/v1/statuses/#{activity.id}") + +    assert %{"id" => id} = json_response(conn, 200) +    assert id == to_string(activity.id) +  end + +  test "get statuses by IDs", %{conn: conn} do +    %{id: id1} = insert(:note_activity) +    %{id: id2} = insert(:note_activity) + +    query_string = "ids[]=#{id1}&ids[]=#{id2}" +    conn = get(conn, "/api/v1/statuses/?#{query_string}") + +    assert [%{"id" => ^id1}, %{"id" => ^id2}] = Enum.sort_by(json_response(conn, :ok), & &1["id"]) +  end + +  describe "deleting a status" do +    test "when you created it", %{conn: conn} do +      activity = insert(:note_activity) +      author = User.get_cached_by_ap_id(activity.data["actor"]) + +      conn = +        conn +        |> assign(:user, author) +        |> delete("/api/v1/statuses/#{activity.id}") + +      assert %{} = json_response(conn, 200) + +      refute Activity.get_by_id(activity.id) +    end + +    test "when you didn't create it", %{conn: conn} do +      activity = insert(:note_activity) +      user = insert(:user) + +      conn = +        conn +        |> assign(:user, user) +        |> delete("/api/v1/statuses/#{activity.id}") + +      assert %{"error" => _} = json_response(conn, 403) + +      assert Activity.get_by_id(activity.id) == activity +    end + +    test "when you're an admin or moderator", %{conn: conn} do +      activity1 = insert(:note_activity) +      activity2 = insert(:note_activity) +      admin = insert(:user, info: %{is_admin: true}) +      moderator = insert(:user, info: %{is_moderator: true}) + +      res_conn = +        conn +        |> assign(:user, admin) +        |> delete("/api/v1/statuses/#{activity1.id}") + +      assert %{} = json_response(res_conn, 200) + +      res_conn = +        conn +        |> assign(:user, moderator) +        |> delete("/api/v1/statuses/#{activity2.id}") + +      assert %{} = json_response(res_conn, 200) + +      refute Activity.get_by_id(activity1.id) +      refute Activity.get_by_id(activity2.id) +    end +  end + +  describe "reblogging" do +    test "reblogs and returns the reblogged status", %{conn: conn} do +      activity = insert(:note_activity) +      user = insert(:user) + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses/#{activity.id}/reblog") + +      assert %{ +               "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}, +               "reblogged" => true +             } = json_response(conn, 200) + +      assert to_string(activity.id) == id +    end + +    test "reblogged status for another user", %{conn: conn} do +      activity = insert(:note_activity) +      user1 = insert(:user) +      user2 = insert(:user) +      user3 = insert(:user) +      CommonAPI.favorite(activity.id, user2) +      {:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id) +      {:ok, reblog_activity1, _object} = CommonAPI.repeat(activity.id, user1) +      {:ok, _, _object} = CommonAPI.repeat(activity.id, user2) + +      conn_res = +        conn +        |> assign(:user, user3) +        |> get("/api/v1/statuses/#{reblog_activity1.id}") + +      assert %{ +               "reblog" => %{"id" => id, "reblogged" => false, "reblogs_count" => 2}, +               "reblogged" => false, +               "favourited" => false, +               "bookmarked" => false +             } = json_response(conn_res, 200) + +      conn_res = +        conn +        |> assign(:user, user2) +        |> get("/api/v1/statuses/#{reblog_activity1.id}") + +      assert %{ +               "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 2}, +               "reblogged" => true, +               "favourited" => true, +               "bookmarked" => true +             } = json_response(conn_res, 200) + +      assert to_string(activity.id) == id +    end + +    test "returns 400 error when activity is not exist", %{conn: conn} do +      user = insert(:user) + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses/foo/reblog") + +      assert json_response(conn, 400) == %{"error" => "Could not repeat"} +    end +  end + +  describe "unreblogging" do +    test "unreblogs and returns the unreblogged status", %{conn: conn} do +      activity = insert(:note_activity) +      user = insert(:user) + +      {:ok, _, _} = CommonAPI.repeat(activity.id, user) + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses/#{activity.id}/unreblog") + +      assert %{"id" => id, "reblogged" => false, "reblogs_count" => 0} = json_response(conn, 200) + +      assert to_string(activity.id) == id +    end + +    test "returns 400 error when activity is not exist", %{conn: conn} do +      user = insert(:user) + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses/foo/unreblog") + +      assert json_response(conn, 400) == %{"error" => "Could not unrepeat"} +    end +  end + +  describe "favoriting" do +    test "favs a status and returns it", %{conn: conn} do +      activity = insert(:note_activity) +      user = insert(:user) + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses/#{activity.id}/favourite") + +      assert %{"id" => id, "favourites_count" => 1, "favourited" => true} = +               json_response(conn, 200) + +      assert to_string(activity.id) == id +    end + +    test "returns 400 error for a wrong id", %{conn: conn} do +      user = insert(:user) + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses/1/favourite") + +      assert json_response(conn, 400) == %{"error" => "Could not favorite"} +    end +  end + +  describe "unfavoriting" do +    test "unfavorites a status and returns it", %{conn: conn} do +      activity = insert(:note_activity) +      user = insert(:user) + +      {:ok, _, _} = CommonAPI.favorite(activity.id, user) + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses/#{activity.id}/unfavourite") + +      assert %{"id" => id, "favourites_count" => 0, "favourited" => false} = +               json_response(conn, 200) + +      assert to_string(activity.id) == id +    end + +    test "returns 400 error for a wrong id", %{conn: conn} do +      user = insert(:user) + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses/1/unfavourite") + +      assert json_response(conn, 400) == %{"error" => "Could not unfavorite"} +    end +  end + +  describe "pinned statuses" do +    setup do +      user = insert(:user) +      {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"}) + +      [user: user, activity: activity] +    end + +    clear_config([:instance, :max_pinned_statuses]) do +      Config.put([:instance, :max_pinned_statuses], 1) +    end + +    test "pin status", %{conn: conn, user: user, activity: activity} do +      id_str = to_string(activity.id) + +      assert %{"id" => ^id_str, "pinned" => true} = +               conn +               |> assign(:user, user) +               |> post("/api/v1/statuses/#{activity.id}/pin") +               |> json_response(200) + +      assert [%{"id" => ^id_str, "pinned" => true}] = +               conn +               |> assign(:user, user) +               |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true") +               |> json_response(200) +    end + +    test "/pin: returns 400 error when activity is not public", %{conn: conn, user: user} do +      {:ok, dm} = CommonAPI.post(user, %{"status" => "test", "visibility" => "direct"}) + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses/#{dm.id}/pin") + +      assert json_response(conn, 400) == %{"error" => "Could not pin"} +    end + +    test "unpin status", %{conn: conn, user: user, activity: activity} do +      {:ok, _} = CommonAPI.pin(activity.id, user) + +      id_str = to_string(activity.id) +      user = refresh_record(user) + +      assert %{"id" => ^id_str, "pinned" => false} = +               conn +               |> assign(:user, user) +               |> post("/api/v1/statuses/#{activity.id}/unpin") +               |> json_response(200) + +      assert [] = +               conn +               |> assign(:user, user) +               |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true") +               |> json_response(200) +    end + +    test "/unpin: returns 400 error when activity is not exist", %{conn: conn, user: user} do +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses/1/unpin") + +      assert json_response(conn, 400) == %{"error" => "Could not unpin"} +    end + +    test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do +      {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"}) + +      id_str_one = to_string(activity_one.id) + +      assert %{"id" => ^id_str_one, "pinned" => true} = +               conn +               |> assign(:user, user) +               |> post("/api/v1/statuses/#{id_str_one}/pin") +               |> json_response(200) + +      user = refresh_record(user) + +      assert %{"error" => "You have already pinned the maximum number of statuses"} = +               conn +               |> assign(:user, user) +               |> post("/api/v1/statuses/#{activity_two.id}/pin") +               |> json_response(400) +    end +  end + +  describe "cards" do +    setup do +      Config.put([:rich_media, :enabled], true) + +      user = insert(:user) +      %{user: user} +    end + +    test "returns rich-media card", %{conn: conn, user: user} do +      Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end) + +      {:ok, activity} = CommonAPI.post(user, %{"status" => "https://example.com/ogp"}) + +      card_data = %{ +        "image" => "http://ia.media-imdb.com/images/rock.jpg", +        "provider_name" => "example.com", +        "provider_url" => "https://example.com", +        "title" => "The Rock", +        "type" => "link", +        "url" => "https://example.com/ogp", +        "description" => +          "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.", +        "pleroma" => %{ +          "opengraph" => %{ +            "image" => "http://ia.media-imdb.com/images/rock.jpg", +            "title" => "The Rock", +            "type" => "video.movie", +            "url" => "https://example.com/ogp", +            "description" => +              "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer." +          } +        } +      } + +      response = +        conn +        |> get("/api/v1/statuses/#{activity.id}/card") +        |> json_response(200) + +      assert response == card_data + +      # works with private posts +      {:ok, activity} = +        CommonAPI.post(user, %{"status" => "https://example.com/ogp", "visibility" => "direct"}) + +      response_two = +        conn +        |> assign(:user, user) +        |> get("/api/v1/statuses/#{activity.id}/card") +        |> json_response(200) + +      assert response_two == card_data +    end + +    test "replaces missing description with an empty string", %{conn: conn, user: user} do +      Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end) + +      {:ok, activity} = +        CommonAPI.post(user, %{"status" => "https://example.com/ogp-missing-data"}) + +      response = +        conn +        |> get("/api/v1/statuses/#{activity.id}/card") +        |> json_response(:ok) + +      assert response == %{ +               "type" => "link", +               "title" => "Pleroma", +               "description" => "", +               "image" => nil, +               "provider_name" => "example.com", +               "provider_url" => "https://example.com", +               "url" => "https://example.com/ogp-missing-data", +               "pleroma" => %{ +                 "opengraph" => %{ +                   "title" => "Pleroma", +                   "type" => "website", +                   "url" => "https://example.com/ogp-missing-data" +                 } +               } +             } +    end +  end + +  test "bookmarks" do +    user = insert(:user) +    for_user = insert(:user) + +    {:ok, activity1} = +      CommonAPI.post(user, %{ +        "status" => "heweoo?" +      }) + +    {:ok, activity2} = +      CommonAPI.post(user, %{ +        "status" => "heweoo!" +      }) + +    response1 = +      build_conn() +      |> assign(:user, for_user) +      |> post("/api/v1/statuses/#{activity1.id}/bookmark") + +    assert json_response(response1, 200)["bookmarked"] == true + +    response2 = +      build_conn() +      |> assign(:user, for_user) +      |> post("/api/v1/statuses/#{activity2.id}/bookmark") + +    assert json_response(response2, 200)["bookmarked"] == true + +    bookmarks = +      build_conn() +      |> assign(:user, for_user) +      |> get("/api/v1/bookmarks") + +    assert [json_response(response2, 200), json_response(response1, 200)] == +             json_response(bookmarks, 200) + +    response1 = +      build_conn() +      |> assign(:user, for_user) +      |> post("/api/v1/statuses/#{activity1.id}/unbookmark") + +    assert json_response(response1, 200)["bookmarked"] == false + +    bookmarks = +      build_conn() +      |> assign(:user, for_user) +      |> get("/api/v1/bookmarks") + +    assert [json_response(response2, 200)] == json_response(bookmarks, 200) +  end + +  describe "conversation muting" do +    setup do +      post_user = insert(:user) +      user = insert(:user) + +      {:ok, activity} = CommonAPI.post(post_user, %{"status" => "HIE"}) + +      [user: user, activity: activity] +    end + +    test "mute conversation", %{conn: conn, user: user, activity: activity} do +      id_str = to_string(activity.id) + +      assert %{"id" => ^id_str, "muted" => true} = +               conn +               |> assign(:user, user) +               |> post("/api/v1/statuses/#{activity.id}/mute") +               |> json_response(200) +    end + +    test "cannot mute already muted conversation", %{conn: conn, user: user, activity: activity} do +      {:ok, _} = CommonAPI.add_mute(user, activity) + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/statuses/#{activity.id}/mute") + +      assert json_response(conn, 400) == %{"error" => "conversation is already muted"} +    end + +    test "unmute conversation", %{conn: conn, user: user, activity: activity} do +      {:ok, _} = CommonAPI.add_mute(user, activity) + +      id_str = to_string(activity.id) +      user = refresh_record(user) + +      assert %{"id" => ^id_str, "muted" => false} = +               conn +               |> assign(:user, user) +               |> post("/api/v1/statuses/#{activity.id}/unmute") +               |> json_response(200) +    end +  end + +  test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do +    user1 = insert(:user) +    user2 = insert(:user) +    user3 = insert(:user) + +    {:ok, replied_to} = CommonAPI.post(user1, %{"status" => "cofe"}) + +    # Reply to status from another user +    conn1 = +      conn +      |> assign(:user, user2) +      |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id}) + +    assert %{"content" => "xD", "id" => id} = json_response(conn1, 200) + +    activity = Activity.get_by_id_with_object(id) + +    assert Object.normalize(activity).data["inReplyTo"] == Object.normalize(replied_to).data["id"] +    assert Activity.get_in_reply_to_activity(activity).id == replied_to.id + +    # Reblog from the third user +    conn2 = +      conn +      |> assign(:user, user3) +      |> post("/api/v1/statuses/#{activity.id}/reblog") + +    assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} = +             json_response(conn2, 200) + +    assert to_string(activity.id) == id + +    # Getting third user status +    conn3 = +      conn +      |> assign(:user, user3) +      |> get("api/v1/timelines/home") + +    [reblogged_activity] = json_response(conn3, 200) + +    assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id + +    replied_to_user = User.get_by_ap_id(replied_to.data["actor"]) +    assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id +  end + +  describe "GET /api/v1/statuses/:id/favourited_by" do +    setup do +      user = insert(:user) +      {:ok, activity} = CommonAPI.post(user, %{"status" => "test"}) + +      conn = +        build_conn() +        |> assign(:user, user) + +      [conn: conn, activity: activity, user: user] +    end + +    test "returns users who have favorited the status", %{conn: conn, activity: activity} do +      other_user = insert(:user) +      {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) + +      response = +        conn +        |> get("/api/v1/statuses/#{activity.id}/favourited_by") +        |> json_response(:ok) + +      [%{"id" => id}] = response + +      assert id == other_user.id +    end + +    test "returns empty array when status has not been favorited yet", %{ +      conn: conn, +      activity: activity +    } do +      response = +        conn +        |> get("/api/v1/statuses/#{activity.id}/favourited_by") +        |> json_response(:ok) + +      assert Enum.empty?(response) +    end + +    test "does not return users who have favorited the status but are blocked", %{ +      conn: %{assigns: %{user: user}} = conn, +      activity: activity +    } do +      other_user = insert(:user) +      {:ok, user} = User.block(user, other_user) + +      {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) + +      response = +        conn +        |> assign(:user, user) +        |> get("/api/v1/statuses/#{activity.id}/favourited_by") +        |> json_response(:ok) + +      assert Enum.empty?(response) +    end + +    test "does not fail on an unauthenticated request", %{conn: conn, activity: activity} do +      other_user = insert(:user) +      {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) + +      response = +        conn +        |> assign(:user, nil) +        |> get("/api/v1/statuses/#{activity.id}/favourited_by") +        |> json_response(:ok) + +      [%{"id" => id}] = response +      assert id == other_user.id +    end + +    test "requires authentification for private posts", %{conn: conn, user: user} do +      other_user = insert(:user) + +      {:ok, activity} = +        CommonAPI.post(user, %{ +          "status" => "@#{other_user.nickname} wanna get some #cofe together?", +          "visibility" => "direct" +        }) + +      {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) + +      conn +      |> assign(:user, nil) +      |> get("/api/v1/statuses/#{activity.id}/favourited_by") +      |> json_response(404) + +      response = +        build_conn() +        |> assign(:user, other_user) +        |> get("/api/v1/statuses/#{activity.id}/favourited_by") +        |> json_response(200) + +      [%{"id" => id}] = response +      assert id == other_user.id +    end +  end + +  describe "GET /api/v1/statuses/:id/reblogged_by" do +    setup do +      user = insert(:user) +      {:ok, activity} = CommonAPI.post(user, %{"status" => "test"}) + +      conn = +        build_conn() +        |> assign(:user, user) + +      [conn: conn, activity: activity, user: user] +    end + +    test "returns users who have reblogged the status", %{conn: conn, activity: activity} do +      other_user = insert(:user) +      {:ok, _, _} = CommonAPI.repeat(activity.id, other_user) + +      response = +        conn +        |> get("/api/v1/statuses/#{activity.id}/reblogged_by") +        |> json_response(:ok) + +      [%{"id" => id}] = response + +      assert id == other_user.id +    end + +    test "returns empty array when status has not been reblogged yet", %{ +      conn: conn, +      activity: activity +    } do +      response = +        conn +        |> get("/api/v1/statuses/#{activity.id}/reblogged_by") +        |> json_response(:ok) + +      assert Enum.empty?(response) +    end + +    test "does not return users who have reblogged the status but are blocked", %{ +      conn: %{assigns: %{user: user}} = conn, +      activity: activity +    } do +      other_user = insert(:user) +      {:ok, user} = User.block(user, other_user) + +      {:ok, _, _} = CommonAPI.repeat(activity.id, other_user) + +      response = +        conn +        |> assign(:user, user) +        |> get("/api/v1/statuses/#{activity.id}/reblogged_by") +        |> json_response(:ok) + +      assert Enum.empty?(response) +    end + +    test "does not fail on an unauthenticated request", %{conn: conn, activity: activity} do +      other_user = insert(:user) +      {:ok, _, _} = CommonAPI.repeat(activity.id, other_user) + +      response = +        conn +        |> assign(:user, nil) +        |> get("/api/v1/statuses/#{activity.id}/reblogged_by") +        |> json_response(:ok) + +      [%{"id" => id}] = response +      assert id == other_user.id +    end + +    test "requires authentification for private posts", %{conn: conn, user: user} do +      other_user = insert(:user) + +      {:ok, activity} = +        CommonAPI.post(user, %{ +          "status" => "@#{other_user.nickname} wanna get some #cofe together?", +          "visibility" => "direct" +        }) + +      conn +      |> assign(:user, nil) +      |> get("/api/v1/statuses/#{activity.id}/reblogged_by") +      |> json_response(404) + +      response = +        build_conn() +        |> assign(:user, other_user) +        |> get("/api/v1/statuses/#{activity.id}/reblogged_by") +        |> json_response(200) + +      assert [] == response +    end +  end + +  test "context" do +    user = insert(:user) + +    {:ok, %{id: id1}} = CommonAPI.post(user, %{"status" => "1"}) +    {:ok, %{id: id2}} = CommonAPI.post(user, %{"status" => "2", "in_reply_to_status_id" => id1}) +    {:ok, %{id: id3}} = CommonAPI.post(user, %{"status" => "3", "in_reply_to_status_id" => id2}) +    {:ok, %{id: id4}} = CommonAPI.post(user, %{"status" => "4", "in_reply_to_status_id" => id3}) +    {:ok, %{id: id5}} = CommonAPI.post(user, %{"status" => "5", "in_reply_to_status_id" => id4}) + +    response = +      build_conn() +      |> assign(:user, nil) +      |> get("/api/v1/statuses/#{id3}/context") +      |> json_response(:ok) + +    assert %{ +             "ancestors" => [%{"id" => ^id1}, %{"id" => ^id2}], +             "descendants" => [%{"id" => ^id4}, %{"id" => ^id5}] +           } = response +  end +end diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index 7f7a89516..b3acb7a22 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -6,18 +6,14 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do    use Pleroma.Web.ConnCase    alias Ecto.Changeset -  alias Pleroma.Activity -  alias Pleroma.ActivityExpiration    alias Pleroma.Config    alias Pleroma.Notification    alias Pleroma.Object    alias Pleroma.Repo -  alias Pleroma.ScheduledActivity    alias Pleroma.Tests.ObanHelpers    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.CommonAPI -  alias Pleroma.Web.MastodonAPI.FilterView    alias Pleroma.Web.OAuth.App    alias Pleroma.Web.OAuth.Token    alias Pleroma.Web.Push @@ -37,312 +33,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do    clear_config([:instance, :public])    clear_config([:rich_media, :enabled]) -  describe "posting statuses" do -    setup do -      user = insert(:user) - -      conn = -        build_conn() -        |> assign(:user, user) - -      [conn: conn] -    end - -    test "posting a status", %{conn: conn} do -      idempotency_key = "Pikachu rocks!" - -      conn_one = -        conn -        |> put_req_header("idempotency-key", idempotency_key) -        |> post("/api/v1/statuses", %{ -          "status" => "cofe", -          "spoiler_text" => "2hu", -          "sensitive" => "false" -        }) - -      {:ok, ttl} = Cachex.ttl(:idempotency_cache, idempotency_key) -      # Six hours -      assert ttl > :timer.seconds(6 * 60 * 60 - 1) - -      assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} = -               json_response(conn_one, 200) - -      assert Activity.get_by_id(id) - -      conn_two = -        conn -        |> put_req_header("idempotency-key", idempotency_key) -        |> post("/api/v1/statuses", %{ -          "status" => "cofe", -          "spoiler_text" => "2hu", -          "sensitive" => "false" -        }) - -      assert %{"id" => second_id} = json_response(conn_two, 200) -      assert id == second_id - -      conn_three = -        conn -        |> post("/api/v1/statuses", %{ -          "status" => "cofe", -          "spoiler_text" => "2hu", -          "sensitive" => "false" -        }) - -      assert %{"id" => third_id} = json_response(conn_three, 200) -      refute id == third_id - -      # An activity that will expire: -      # 2 hours -      expires_in = 120 * 60 - -      conn_four = -        conn -        |> post("api/v1/statuses", %{ -          "status" => "oolong", -          "expires_in" => expires_in -        }) - -      assert fourth_response = %{"id" => fourth_id} = json_response(conn_four, 200) -      assert activity = Activity.get_by_id(fourth_id) -      assert expiration = ActivityExpiration.get_by_activity_id(fourth_id) - -      estimated_expires_at = -        NaiveDateTime.utc_now() -        |> NaiveDateTime.add(expires_in) -        |> NaiveDateTime.truncate(:second) - -      # This assert will fail if the test takes longer than a minute. I sure hope it never does: -      assert abs(NaiveDateTime.diff(expiration.scheduled_at, estimated_expires_at, :second)) < 60 - -      assert fourth_response["pleroma"]["expires_at"] == -               NaiveDateTime.to_iso8601(expiration.scheduled_at) -    end - -    test "replying to a status", %{conn: conn} do -      user = insert(:user) -      {:ok, replied_to} = CommonAPI.post(user, %{"status" => "cofe"}) - -      conn = -        conn -        |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id}) - -      assert %{"content" => "xD", "id" => id} = json_response(conn, 200) - -      activity = Activity.get_by_id(id) - -      assert activity.data["context"] == replied_to.data["context"] -      assert Activity.get_in_reply_to_activity(activity).id == replied_to.id -    end - -    test "replying to a direct message with visibility other than direct", %{conn: conn} do -      user = insert(:user) -      {:ok, replied_to} = CommonAPI.post(user, %{"status" => "suya..", "visibility" => "direct"}) - -      Enum.each(["public", "private", "unlisted"], fn visibility -> -        conn = -          conn -          |> post("/api/v1/statuses", %{ -            "status" => "@#{user.nickname} hey", -            "in_reply_to_id" => replied_to.id, -            "visibility" => visibility -          }) - -        assert json_response(conn, 422) == %{"error" => "The message visibility must be direct"} -      end) -    end - -    test "posting a status with an invalid in_reply_to_id", %{conn: conn} do -      conn = -        conn -        |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => ""}) - -      assert %{"content" => "xD", "id" => id} = json_response(conn, 200) -      assert Activity.get_by_id(id) -    end - -    test "posting a sensitive status", %{conn: conn} do -      conn = -        conn -        |> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true}) - -      assert %{"content" => "cofe", "id" => id, "sensitive" => true} = json_response(conn, 200) -      assert Activity.get_by_id(id) -    end - -    test "posting a fake status", %{conn: conn} do -      real_conn = -        conn -        |> post("/api/v1/statuses", %{ -          "status" => -            "\"Tenshi Eating a Corndog\" is a much discussed concept on /jp/. The significance of it is disputed, so I will focus on one core concept: the symbolism behind it" -        }) - -      real_status = json_response(real_conn, 200) - -      assert real_status -      assert Object.get_by_ap_id(real_status["uri"]) - -      real_status = -        real_status -        |> Map.put("id", nil) -        |> Map.put("url", nil) -        |> Map.put("uri", nil) -        |> Map.put("created_at", nil) -        |> Kernel.put_in(["pleroma", "conversation_id"], nil) - -      fake_conn = -        conn -        |> post("/api/v1/statuses", %{ -          "status" => -            "\"Tenshi Eating a Corndog\" is a much discussed concept on /jp/. The significance of it is disputed, so I will focus on one core concept: the symbolism behind it", -          "preview" => true -        }) - -      fake_status = json_response(fake_conn, 200) - -      assert fake_status -      refute Object.get_by_ap_id(fake_status["uri"]) - -      fake_status = -        fake_status -        |> Map.put("id", nil) -        |> Map.put("url", nil) -        |> Map.put("uri", nil) -        |> Map.put("created_at", nil) -        |> Kernel.put_in(["pleroma", "conversation_id"], nil) - -      assert real_status == fake_status -    end - -    test "posting a status with OGP link preview", %{conn: conn} do -      Config.put([:rich_media, :enabled], true) - -      conn = -        conn -        |> post("/api/v1/statuses", %{ -          "status" => "https://example.com/ogp" -        }) - -      assert %{"id" => id, "card" => %{"title" => "The Rock"}} = json_response(conn, 200) -      assert Activity.get_by_id(id) -    end - -    test "posting a direct status", %{conn: conn} do -      user2 = insert(:user) -      content = "direct cofe @#{user2.nickname}" - -      conn = -        conn -        |> post("api/v1/statuses", %{"status" => content, "visibility" => "direct"}) - -      assert %{"id" => id} = response = json_response(conn, 200) -      assert response["visibility"] == "direct" -      assert response["pleroma"]["direct_conversation_id"] -      assert activity = Activity.get_by_id(id) -      assert activity.recipients == [user2.ap_id, conn.assigns[:user].ap_id] -      assert activity.data["to"] == [user2.ap_id] -      assert activity.data["cc"] == [] -    end -  end - -  describe "posting polls" do -    test "posting a poll", %{conn: conn} do -      user = insert(:user) -      time = NaiveDateTime.utc_now() - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses", %{ -          "status" => "Who is the #bestgrill?", -          "poll" => %{"options" => ["Rei", "Asuka", "Misato"], "expires_in" => 420} -        }) - -      response = json_response(conn, 200) - -      assert Enum.all?(response["poll"]["options"], fn %{"title" => title} -> -               title in ["Rei", "Asuka", "Misato"] -             end) - -      assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430 -      refute response["poll"]["expred"] -    end - -    test "option limit is enforced", %{conn: conn} do -      user = insert(:user) -      limit = Config.get([:instance, :poll_limits, :max_options]) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses", %{ -          "status" => "desu~", -          "poll" => %{"options" => Enum.map(0..limit, fn _ -> "desu" end), "expires_in" => 1} -        }) - -      %{"error" => error} = json_response(conn, 422) -      assert error == "Poll can't contain more than #{limit} options" -    end - -    test "option character limit is enforced", %{conn: conn} do -      user = insert(:user) -      limit = Config.get([:instance, :poll_limits, :max_option_chars]) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses", %{ -          "status" => "...", -          "poll" => %{ -            "options" => [Enum.reduce(0..limit, "", fn _, acc -> acc <> "." end)], -            "expires_in" => 1 -          } -        }) - -      %{"error" => error} = json_response(conn, 422) -      assert error == "Poll options cannot be longer than #{limit} characters each" -    end - -    test "minimal date limit is enforced", %{conn: conn} do -      user = insert(:user) -      limit = Config.get([:instance, :poll_limits, :min_expiration]) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses", %{ -          "status" => "imagine arbitrary limits", -          "poll" => %{ -            "options" => ["this post was made by pleroma gang"], -            "expires_in" => limit - 1 -          } -        }) - -      %{"error" => error} = json_response(conn, 422) -      assert error == "Expiration date is too soon" -    end - -    test "maximum date limit is enforced", %{conn: conn} do -      user = insert(:user) -      limit = Config.get([:instance, :poll_limits, :max_expiration]) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses", %{ -          "status" => "imagine arbitrary limits", -          "poll" => %{ -            "options" => ["this post was made by pleroma gang"], -            "expires_in" => limit + 1 -          } -        }) - -      %{"error" => error} = json_response(conn, 422) -      assert error == "Expiration date is too far in the future" -    end -  end -    test "Conversations", %{conn: conn} do      user_one = insert(:user)      user_two = insert(:user) @@ -575,363 +265,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      assert expected == json_response(conn, 200)    end -  test "get a status", %{conn: conn} do -    activity = insert(:note_activity) - -    conn = -      conn -      |> get("/api/v1/statuses/#{activity.id}") - -    assert %{"id" => id} = json_response(conn, 200) -    assert id == to_string(activity.id) -  end - -  test "get statuses by IDs", %{conn: conn} do -    %{id: id1} = insert(:note_activity) -    %{id: id2} = insert(:note_activity) - -    query_string = "ids[]=#{id1}&ids[]=#{id2}" -    conn = get(conn, "/api/v1/statuses/?#{query_string}") - -    assert [%{"id" => ^id1}, %{"id" => ^id2}] = Enum.sort_by(json_response(conn, :ok), & &1["id"]) -  end - -  describe "deleting a status" do -    test "when you created it", %{conn: conn} do -      activity = insert(:note_activity) -      author = User.get_cached_by_ap_id(activity.data["actor"]) - -      conn = -        conn -        |> assign(:user, author) -        |> delete("/api/v1/statuses/#{activity.id}") - -      assert %{} = json_response(conn, 200) - -      refute Activity.get_by_id(activity.id) -    end - -    test "when you didn't create it", %{conn: conn} do -      activity = insert(:note_activity) -      user = insert(:user) - -      conn = -        conn -        |> assign(:user, user) -        |> delete("/api/v1/statuses/#{activity.id}") - -      assert %{"error" => _} = json_response(conn, 403) - -      assert Activity.get_by_id(activity.id) == activity -    end - -    test "when you're an admin or moderator", %{conn: conn} do -      activity1 = insert(:note_activity) -      activity2 = insert(:note_activity) -      admin = insert(:user, info: %{is_admin: true}) -      moderator = insert(:user, info: %{is_moderator: true}) - -      res_conn = -        conn -        |> assign(:user, admin) -        |> delete("/api/v1/statuses/#{activity1.id}") - -      assert %{} = json_response(res_conn, 200) - -      res_conn = -        conn -        |> assign(:user, moderator) -        |> delete("/api/v1/statuses/#{activity2.id}") - -      assert %{} = json_response(res_conn, 200) - -      refute Activity.get_by_id(activity1.id) -      refute Activity.get_by_id(activity2.id) -    end -  end - -  describe "filters" do -    test "creating a filter", %{conn: conn} do -      user = insert(:user) - -      filter = %Pleroma.Filter{ -        phrase: "knights", -        context: ["home"] -      } - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/filters", %{"phrase" => filter.phrase, context: filter.context}) - -      assert response = json_response(conn, 200) -      assert response["phrase"] == filter.phrase -      assert response["context"] == filter.context -      assert response["irreversible"] == false -      assert response["id"] != nil -      assert response["id"] != "" -    end - -    test "fetching a list of filters", %{conn: conn} do -      user = insert(:user) - -      query_one = %Pleroma.Filter{ -        user_id: user.id, -        filter_id: 1, -        phrase: "knights", -        context: ["home"] -      } - -      query_two = %Pleroma.Filter{ -        user_id: user.id, -        filter_id: 2, -        phrase: "who", -        context: ["home"] -      } - -      {:ok, filter_one} = Pleroma.Filter.create(query_one) -      {:ok, filter_two} = Pleroma.Filter.create(query_two) - -      response = -        conn -        |> assign(:user, user) -        |> get("/api/v1/filters") -        |> json_response(200) - -      assert response == -               render_json( -                 FilterView, -                 "filters.json", -                 filters: [filter_two, filter_one] -               ) -    end - -    test "get a filter", %{conn: conn} do -      user = insert(:user) - -      query = %Pleroma.Filter{ -        user_id: user.id, -        filter_id: 2, -        phrase: "knight", -        context: ["home"] -      } - -      {:ok, filter} = Pleroma.Filter.create(query) - -      conn = -        conn -        |> assign(:user, user) -        |> get("/api/v1/filters/#{filter.filter_id}") - -      assert _response = json_response(conn, 200) -    end - -    test "update a filter", %{conn: conn} do -      user = insert(:user) - -      query = %Pleroma.Filter{ -        user_id: user.id, -        filter_id: 2, -        phrase: "knight", -        context: ["home"] -      } - -      {:ok, _filter} = Pleroma.Filter.create(query) - -      new = %Pleroma.Filter{ -        phrase: "nii", -        context: ["home"] -      } - -      conn = -        conn -        |> assign(:user, user) -        |> put("/api/v1/filters/#{query.filter_id}", %{ -          phrase: new.phrase, -          context: new.context -        }) - -      assert response = json_response(conn, 200) -      assert response["phrase"] == new.phrase -      assert response["context"] == new.context -    end - -    test "delete a filter", %{conn: conn} do -      user = insert(:user) - -      query = %Pleroma.Filter{ -        user_id: user.id, -        filter_id: 2, -        phrase: "knight", -        context: ["home"] -      } - -      {:ok, filter} = Pleroma.Filter.create(query) - -      conn = -        conn -        |> assign(:user, user) -        |> delete("/api/v1/filters/#{filter.filter_id}") - -      assert response = json_response(conn, 200) -      assert response == %{} -    end -  end - -  describe "reblogging" do -    test "reblogs and returns the reblogged status", %{conn: conn} do -      activity = insert(:note_activity) -      user = insert(:user) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses/#{activity.id}/reblog") - -      assert %{ -               "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}, -               "reblogged" => true -             } = json_response(conn, 200) - -      assert to_string(activity.id) == id -    end - -    test "reblogged status for another user", %{conn: conn} do -      activity = insert(:note_activity) -      user1 = insert(:user) -      user2 = insert(:user) -      user3 = insert(:user) -      CommonAPI.favorite(activity.id, user2) -      {:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id) -      {:ok, reblog_activity1, _object} = CommonAPI.repeat(activity.id, user1) -      {:ok, _, _object} = CommonAPI.repeat(activity.id, user2) - -      conn_res = -        conn -        |> assign(:user, user3) -        |> get("/api/v1/statuses/#{reblog_activity1.id}") - -      assert %{ -               "reblog" => %{"id" => id, "reblogged" => false, "reblogs_count" => 2}, -               "reblogged" => false, -               "favourited" => false, -               "bookmarked" => false -             } = json_response(conn_res, 200) - -      conn_res = -        conn -        |> assign(:user, user2) -        |> get("/api/v1/statuses/#{reblog_activity1.id}") - -      assert %{ -               "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 2}, -               "reblogged" => true, -               "favourited" => true, -               "bookmarked" => true -             } = json_response(conn_res, 200) - -      assert to_string(activity.id) == id -    end - -    test "returns 400 error when activity is not exist", %{conn: conn} do -      user = insert(:user) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses/foo/reblog") - -      assert json_response(conn, 400) == %{"error" => "Could not repeat"} -    end -  end - -  describe "unreblogging" do -    test "unreblogs and returns the unreblogged status", %{conn: conn} do -      activity = insert(:note_activity) -      user = insert(:user) - -      {:ok, _, _} = CommonAPI.repeat(activity.id, user) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses/#{activity.id}/unreblog") - -      assert %{"id" => id, "reblogged" => false, "reblogs_count" => 0} = json_response(conn, 200) - -      assert to_string(activity.id) == id -    end - -    test "returns 400 error when activity is not exist", %{conn: conn} do -      user = insert(:user) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses/foo/unreblog") - -      assert json_response(conn, 400) == %{"error" => "Could not unrepeat"} -    end -  end - -  describe "favoriting" do -    test "favs a status and returns it", %{conn: conn} do -      activity = insert(:note_activity) -      user = insert(:user) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses/#{activity.id}/favourite") - -      assert %{"id" => id, "favourites_count" => 1, "favourited" => true} = -               json_response(conn, 200) - -      assert to_string(activity.id) == id -    end - -    test "returns 400 error for a wrong id", %{conn: conn} do -      user = insert(:user) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses/1/favourite") - -      assert json_response(conn, 400) == %{"error" => "Could not favorite"} -    end -  end - -  describe "unfavoriting" do -    test "unfavorites a status and returns it", %{conn: conn} do -      activity = insert(:note_activity) -      user = insert(:user) - -      {:ok, _, _} = CommonAPI.favorite(activity.id, user) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses/#{activity.id}/unfavourite") - -      assert %{"id" => id, "favourites_count" => 0, "favourited" => false} = -               json_response(conn, 200) - -      assert to_string(activity.id) == id -    end - -    test "returns 400 error for a wrong id", %{conn: conn} do -      user = insert(:user) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses/1/unfavourite") - -      assert json_response(conn, 400) == %{"error" => "Could not unfavorite"} -    end -  end -    describe "user timelines" do      test "gets a users statuses", %{conn: conn} do        user_one = insert(:user) @@ -1066,6 +399,17 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do        assert to_string(other_user.id) == relationship["id"]      end + +    test "returns an empty list on a bad request", %{conn: conn} do +      user = insert(:user) + +      conn = +        conn +        |> assign(:user, user) +        |> get("/api/v1/accounts/relationships", %{}) + +      assert [] = json_response(conn, 200) +    end    end    describe "media upload" do @@ -1106,51 +450,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do    end    describe "locked accounts" do -    test "/api/v1/follow_requests works" do -      user = insert(:user, %{info: %User.Info{locked: true}}) -      other_user = insert(:user) - -      {:ok, _activity} = ActivityPub.follow(other_user, user) - -      user = User.get_cached_by_id(user.id) -      other_user = User.get_cached_by_id(other_user.id) - -      assert User.following?(other_user, user) == false - -      conn = -        build_conn() -        |> assign(:user, user) -        |> get("/api/v1/follow_requests") - -      assert [relationship] = json_response(conn, 200) -      assert to_string(other_user.id) == relationship["id"] -    end - -    test "/api/v1/follow_requests/:id/authorize works" do -      user = insert(:user, %{info: %User.Info{locked: true}}) -      other_user = insert(:user) - -      {:ok, _activity} = ActivityPub.follow(other_user, user) - -      user = User.get_cached_by_id(user.id) -      other_user = User.get_cached_by_id(other_user.id) - -      assert User.following?(other_user, user) == false - -      conn = -        build_conn() -        |> assign(:user, user) -        |> post("/api/v1/follow_requests/#{other_user.id}/authorize") - -      assert relationship = json_response(conn, 200) -      assert to_string(other_user.id) == relationship["id"] - -      user = User.get_cached_by_id(user.id) -      other_user = User.get_cached_by_id(other_user.id) - -      assert User.following?(other_user, user) == true -    end -      test "verify_credentials", %{conn: conn} do        user = insert(:user, %{info: %User.Info{default_scope: "private"}}) @@ -1162,28 +461,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do        assert %{"id" => id, "source" => %{"privacy" => "private"}} = json_response(conn, 200)        assert id == to_string(user.id)      end - -    test "/api/v1/follow_requests/:id/reject works" do -      user = insert(:user, %{info: %User.Info{locked: true}}) -      other_user = insert(:user) - -      {:ok, _activity} = ActivityPub.follow(other_user, user) - -      user = User.get_cached_by_id(user.id) - -      conn = -        build_conn() -        |> assign(:user, user) -        |> post("/api/v1/follow_requests/#{other_user.id}/reject") - -      assert relationship = json_response(conn, 200) -      assert to_string(other_user.id) == relationship["id"] - -      user = User.get_cached_by_id(user.id) -      other_user = User.get_cached_by_id(other_user.id) - -      assert User.following?(other_user, user) == false -    end    end    describe "account fetching" do @@ -1267,70 +544,72 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      end    end -  test "mascot upload", %{conn: conn} do -    user = insert(:user) +  describe "/api/v1/pleroma/mascot" do +    test "mascot upload", %{conn: conn} do +      user = insert(:user) -    non_image_file = %Plug.Upload{ -      content_type: "audio/mpeg", -      path: Path.absname("test/fixtures/sound.mp3"), -      filename: "sound.mp3" -    } +      non_image_file = %Plug.Upload{ +        content_type: "audio/mpeg", +        path: Path.absname("test/fixtures/sound.mp3"), +        filename: "sound.mp3" +      } -    conn = -      conn -      |> assign(:user, user) -      |> put("/api/v1/pleroma/mascot", %{"file" => non_image_file}) +      conn = +        conn +        |> assign(:user, user) +        |> put("/api/v1/pleroma/mascot", %{"file" => non_image_file}) -    assert json_response(conn, 415) +      assert json_response(conn, 415) -    file = %Plug.Upload{ -      content_type: "image/jpg", -      path: Path.absname("test/fixtures/image.jpg"), -      filename: "an_image.jpg" -    } +      file = %Plug.Upload{ +        content_type: "image/jpg", +        path: Path.absname("test/fixtures/image.jpg"), +        filename: "an_image.jpg" +      } -    conn = -      build_conn() -      |> assign(:user, user) -      |> put("/api/v1/pleroma/mascot", %{"file" => file}) +      conn = +        build_conn() +        |> assign(:user, user) +        |> put("/api/v1/pleroma/mascot", %{"file" => file}) -    assert %{"id" => _, "type" => image} = json_response(conn, 200) -  end +      assert %{"id" => _, "type" => image} = json_response(conn, 200) +    end -  test "mascot retrieving", %{conn: conn} do -    user = insert(:user) -    # When user hasn't set a mascot, we should just get pleroma tan back -    conn = -      conn -      |> assign(:user, user) -      |> get("/api/v1/pleroma/mascot") +    test "mascot retrieving", %{conn: conn} do +      user = insert(:user) +      # When user hasn't set a mascot, we should just get pleroma tan back +      conn = +        conn +        |> assign(:user, user) +        |> get("/api/v1/pleroma/mascot") -    assert %{"url" => url} = json_response(conn, 200) -    assert url =~ "pleroma-fox-tan-smol" +      assert %{"url" => url} = json_response(conn, 200) +      assert url =~ "pleroma-fox-tan-smol" -    # When a user sets their mascot, we should get that back -    file = %Plug.Upload{ -      content_type: "image/jpg", -      path: Path.absname("test/fixtures/image.jpg"), -      filename: "an_image.jpg" -    } +      # When a user sets their mascot, we should get that back +      file = %Plug.Upload{ +        content_type: "image/jpg", +        path: Path.absname("test/fixtures/image.jpg"), +        filename: "an_image.jpg" +      } -    conn = -      build_conn() -      |> assign(:user, user) -      |> put("/api/v1/pleroma/mascot", %{"file" => file}) +      conn = +        build_conn() +        |> assign(:user, user) +        |> put("/api/v1/pleroma/mascot", %{"file" => file}) -    assert json_response(conn, 200) +      assert json_response(conn, 200) -    user = User.get_cached_by_id(user.id) +      user = User.get_cached_by_id(user.id) -    conn = -      build_conn() -      |> assign(:user, user) -      |> get("/api/v1/pleroma/mascot") +      conn = +        build_conn() +        |> assign(:user, user) +        |> get("/api/v1/pleroma/mascot") -    assert %{"url" => url, "type" => "image"} = json_response(conn, 200) -    assert url =~ "an_image" +      assert %{"url" => url, "type" => "image"} = json_response(conn, 200) +      assert url =~ "an_image" +    end    end    test "getting followers", %{conn: conn} do @@ -1642,23 +921,51 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      end    end -  test "subscribing / unsubscribing to a user", %{conn: conn} do -    user = insert(:user) -    subscription_target = insert(:user) +  describe "subscribing / unsubscribing" do +    test "subscribing / unsubscribing to a user", %{conn: conn} do +      user = insert(:user) +      subscription_target = insert(:user) -    conn = -      conn -      |> assign(:user, user) -      |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/subscribe") +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/subscribe") -    assert %{"id" => _id, "subscribing" => true} = json_response(conn, 200) +      assert %{"id" => _id, "subscribing" => true} = json_response(conn, 200) -    conn = -      build_conn() -      |> assign(:user, user) -      |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/unsubscribe") +      conn = +        build_conn() +        |> assign(:user, user) +        |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/unsubscribe") -    assert %{"id" => _id, "subscribing" => false} = json_response(conn, 200) +      assert %{"id" => _id, "subscribing" => false} = json_response(conn, 200) +    end +  end + +  describe "subscribing" do +    test "returns 404 when subscription_target not found", %{conn: conn} do +      user = insert(:user) + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/pleroma/accounts/target_id/subscribe") + +      assert %{"error" => "Record not found"} = json_response(conn, 404) +    end +  end + +  describe "unsubscribing" do +    test "returns 404 when subscription_target not found", %{conn: conn} do +      user = insert(:user) + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/pleroma/accounts/target_id/unsubscribe") + +      assert %{"error" => "Record not found"} = json_response(conn, 404) +    end    end    test "getting a list of mutes", %{conn: conn} do @@ -1712,46 +1019,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      assert [%{"id" => ^other_user_id}] = json_response(conn, 200)    end -  test "blocking / unblocking a domain", %{conn: conn} do -    user = insert(:user) -    other_user = insert(:user, %{ap_id: "https://dogwhistle.zone/@pundit"}) - -    conn = -      conn -      |> assign(:user, user) -      |> post("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"}) - -    assert %{} = json_response(conn, 200) -    user = User.get_cached_by_ap_id(user.ap_id) -    assert User.blocks?(user, other_user) - -    conn = -      build_conn() -      |> assign(:user, user) -      |> delete("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"}) - -    assert %{} = json_response(conn, 200) -    user = User.get_cached_by_ap_id(user.ap_id) -    refute User.blocks?(user, other_user) -  end - -  test "getting a list of domain blocks", %{conn: conn} do -    user = insert(:user) - -    {:ok, user} = User.block_domain(user, "bad.site") -    {:ok, user} = User.block_domain(user, "even.worse.site") - -    conn = -      conn -      |> assign(:user, user) -      |> get("/api/v1/domain_blocks") - -    domain_blocks = json_response(conn, 200) - -    assert "bad.site" in domain_blocks -    assert "even.worse.site" in domain_blocks -  end -    test "unimplemented follow_requests, blocks, domain blocks" do      user = insert(:user) @@ -2098,10 +1365,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do        [user: user, activity: activity]      end -    clear_config([:instance, :max_pinned_statuses]) do -      Config.put([:instance, :max_pinned_statuses], 1) -    end -      test "returns pinned statuses", %{conn: conn, user: user, activity: activity} do        {:ok, _} = CommonAPI.pin(activity.id, user) @@ -2115,257 +1378,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do        assert [%{"id" => ^id_str, "pinned" => true}] = result      end - -    test "pin status", %{conn: conn, user: user, activity: activity} do -      id_str = to_string(activity.id) - -      assert %{"id" => ^id_str, "pinned" => true} = -               conn -               |> assign(:user, user) -               |> post("/api/v1/statuses/#{activity.id}/pin") -               |> json_response(200) - -      assert [%{"id" => ^id_str, "pinned" => true}] = -               conn -               |> assign(:user, user) -               |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true") -               |> json_response(200) -    end - -    test "/pin: returns 400 error when activity is not public", %{conn: conn, user: user} do -      {:ok, dm} = CommonAPI.post(user, %{"status" => "test", "visibility" => "direct"}) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses/#{dm.id}/pin") - -      assert json_response(conn, 400) == %{"error" => "Could not pin"} -    end - -    test "unpin status", %{conn: conn, user: user, activity: activity} do -      {:ok, _} = CommonAPI.pin(activity.id, user) - -      id_str = to_string(activity.id) -      user = refresh_record(user) - -      assert %{"id" => ^id_str, "pinned" => false} = -               conn -               |> assign(:user, user) -               |> post("/api/v1/statuses/#{activity.id}/unpin") -               |> json_response(200) - -      assert [] = -               conn -               |> assign(:user, user) -               |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true") -               |> json_response(200) -    end - -    test "/unpin: returns 400 error when activity is not exist", %{conn: conn, user: user} do -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses/1/unpin") - -      assert json_response(conn, 400) == %{"error" => "Could not unpin"} -    end - -    test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do -      {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"}) - -      id_str_one = to_string(activity_one.id) - -      assert %{"id" => ^id_str_one, "pinned" => true} = -               conn -               |> assign(:user, user) -               |> post("/api/v1/statuses/#{id_str_one}/pin") -               |> json_response(200) - -      user = refresh_record(user) - -      assert %{"error" => "You have already pinned the maximum number of statuses"} = -               conn -               |> assign(:user, user) -               |> post("/api/v1/statuses/#{activity_two.id}/pin") -               |> json_response(400) -    end -  end - -  describe "cards" do -    setup do -      Config.put([:rich_media, :enabled], true) - -      user = insert(:user) -      %{user: user} -    end - -    test "returns rich-media card", %{conn: conn, user: user} do -      {:ok, activity} = CommonAPI.post(user, %{"status" => "https://example.com/ogp"}) - -      card_data = %{ -        "image" => "http://ia.media-imdb.com/images/rock.jpg", -        "provider_name" => "example.com", -        "provider_url" => "https://example.com", -        "title" => "The Rock", -        "type" => "link", -        "url" => "https://example.com/ogp", -        "description" => -          "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.", -        "pleroma" => %{ -          "opengraph" => %{ -            "image" => "http://ia.media-imdb.com/images/rock.jpg", -            "title" => "The Rock", -            "type" => "video.movie", -            "url" => "https://example.com/ogp", -            "description" => -              "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer." -          } -        } -      } - -      response = -        conn -        |> get("/api/v1/statuses/#{activity.id}/card") -        |> json_response(200) - -      assert response == card_data - -      # works with private posts -      {:ok, activity} = -        CommonAPI.post(user, %{"status" => "https://example.com/ogp", "visibility" => "direct"}) - -      response_two = -        conn -        |> assign(:user, user) -        |> get("/api/v1/statuses/#{activity.id}/card") -        |> json_response(200) - -      assert response_two == card_data -    end - -    test "replaces missing description with an empty string", %{conn: conn, user: user} do -      {:ok, activity} = -        CommonAPI.post(user, %{"status" => "https://example.com/ogp-missing-data"}) - -      response = -        conn -        |> get("/api/v1/statuses/#{activity.id}/card") -        |> json_response(:ok) - -      assert response == %{ -               "type" => "link", -               "title" => "Pleroma", -               "description" => "", -               "image" => nil, -               "provider_name" => "example.com", -               "provider_url" => "https://example.com", -               "url" => "https://example.com/ogp-missing-data", -               "pleroma" => %{ -                 "opengraph" => %{ -                   "title" => "Pleroma", -                   "type" => "website", -                   "url" => "https://example.com/ogp-missing-data" -                 } -               } -             } -    end -  end - -  test "bookmarks" do -    user = insert(:user) -    for_user = insert(:user) - -    {:ok, activity1} = -      CommonAPI.post(user, %{ -        "status" => "heweoo?" -      }) - -    {:ok, activity2} = -      CommonAPI.post(user, %{ -        "status" => "heweoo!" -      }) - -    response1 = -      build_conn() -      |> assign(:user, for_user) -      |> post("/api/v1/statuses/#{activity1.id}/bookmark") - -    assert json_response(response1, 200)["bookmarked"] == true - -    response2 = -      build_conn() -      |> assign(:user, for_user) -      |> post("/api/v1/statuses/#{activity2.id}/bookmark") - -    assert json_response(response2, 200)["bookmarked"] == true - -    bookmarks = -      build_conn() -      |> assign(:user, for_user) -      |> get("/api/v1/bookmarks") - -    assert [json_response(response2, 200), json_response(response1, 200)] == -             json_response(bookmarks, 200) - -    response1 = -      build_conn() -      |> assign(:user, for_user) -      |> post("/api/v1/statuses/#{activity1.id}/unbookmark") - -    assert json_response(response1, 200)["bookmarked"] == false - -    bookmarks = -      build_conn() -      |> assign(:user, for_user) -      |> get("/api/v1/bookmarks") - -    assert [json_response(response2, 200)] == json_response(bookmarks, 200) -  end - -  describe "conversation muting" do -    setup do -      post_user = insert(:user) -      user = insert(:user) - -      {:ok, activity} = CommonAPI.post(post_user, %{"status" => "HIE"}) - -      [user: user, activity: activity] -    end - -    test "mute conversation", %{conn: conn, user: user, activity: activity} do -      id_str = to_string(activity.id) - -      assert %{"id" => ^id_str, "muted" => true} = -               conn -               |> assign(:user, user) -               |> post("/api/v1/statuses/#{activity.id}/mute") -               |> json_response(200) -    end - -    test "cannot mute already muted conversation", %{conn: conn, user: user, activity: activity} do -      {:ok, _} = CommonAPI.add_mute(user, activity) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses/#{activity.id}/mute") - -      assert json_response(conn, 400) == %{"error" => "conversation is already muted"} -    end - -    test "unmute conversation", %{conn: conn, user: user, activity: activity} do -      {:ok, _} = CommonAPI.add_mute(user, activity) - -      id_str = to_string(activity.id) -      user = refresh_record(user) - -      assert %{"id" => ^id_str, "muted" => false} = -               conn -               |> assign(:user, user) -               |> post("/api/v1/statuses/#{activity.id}/unmute") -               |> json_response(200) -    end    end    describe "reports" do @@ -2601,262 +1613,18 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      end    end -  describe "scheduled activities" do -    test "creates a scheduled activity", %{conn: conn} do -      user = insert(:user) -      scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses", %{ -          "status" => "scheduled", -          "scheduled_at" => scheduled_at -        }) - -      assert %{"scheduled_at" => expected_scheduled_at} = json_response(conn, 200) -      assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(scheduled_at) -      assert [] == Repo.all(Activity) -    end - -    test "creates a scheduled activity with a media attachment", %{conn: conn} do -      user = insert(:user) -      scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond) - -      file = %Plug.Upload{ -        content_type: "image/jpg", -        path: Path.absname("test/fixtures/image.jpg"), -        filename: "an_image.jpg" +  describe "create account by app" do +    setup do +      valid_params = %{ +        username: "lain", +        email: "lain@example.org", +        password: "PlzDontHackLain", +        agreement: true        } -      {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses", %{ -          "media_ids" => [to_string(upload.id)], -          "status" => "scheduled", -          "scheduled_at" => scheduled_at -        }) - -      assert %{"media_attachments" => [media_attachment]} = json_response(conn, 200) -      assert %{"type" => "image"} = media_attachment -    end - -    test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now", -         %{conn: conn} do -      user = insert(:user) - -      scheduled_at = -        NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses", %{ -          "status" => "not scheduled", -          "scheduled_at" => scheduled_at -        }) - -      assert %{"content" => "not scheduled"} = json_response(conn, 200) -      assert [] == Repo.all(ScheduledActivity) +      [valid_params: valid_params]      end -    test "returns error when daily user limit is exceeded", %{conn: conn} do -      user = insert(:user) - -      today = -        NaiveDateTime.utc_now() -        |> NaiveDateTime.add(:timer.minutes(6), :millisecond) -        |> NaiveDateTime.to_iso8601() - -      attrs = %{params: %{}, scheduled_at: today} -      {:ok, _} = ScheduledActivity.create(user, attrs) -      {:ok, _} = ScheduledActivity.create(user, attrs) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today}) - -      assert %{"error" => "daily limit exceeded"} == json_response(conn, 422) -    end - -    test "returns error when total user limit is exceeded", %{conn: conn} do -      user = insert(:user) - -      today = -        NaiveDateTime.utc_now() -        |> NaiveDateTime.add(:timer.minutes(6), :millisecond) -        |> NaiveDateTime.to_iso8601() - -      tomorrow = -        NaiveDateTime.utc_now() -        |> NaiveDateTime.add(:timer.hours(36), :millisecond) -        |> NaiveDateTime.to_iso8601() - -      attrs = %{params: %{}, scheduled_at: today} -      {:ok, _} = ScheduledActivity.create(user, attrs) -      {:ok, _} = ScheduledActivity.create(user, attrs) -      {:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow}) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow}) - -      assert %{"error" => "total limit exceeded"} == json_response(conn, 422) -    end - -    test "shows scheduled activities", %{conn: conn} do -      user = insert(:user) -      scheduled_activity_id1 = insert(:scheduled_activity, user: user).id |> to_string() -      scheduled_activity_id2 = insert(:scheduled_activity, user: user).id |> to_string() -      scheduled_activity_id3 = insert(:scheduled_activity, user: user).id |> to_string() -      scheduled_activity_id4 = insert(:scheduled_activity, user: user).id |> to_string() - -      conn = -        conn -        |> assign(:user, user) - -      # min_id -      conn_res = -        conn -        |> get("/api/v1/scheduled_statuses?limit=2&min_id=#{scheduled_activity_id1}") - -      result = json_response(conn_res, 200) -      assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result - -      # since_id -      conn_res = -        conn -        |> get("/api/v1/scheduled_statuses?limit=2&since_id=#{scheduled_activity_id1}") - -      result = json_response(conn_res, 200) -      assert [%{"id" => ^scheduled_activity_id4}, %{"id" => ^scheduled_activity_id3}] = result - -      # max_id -      conn_res = -        conn -        |> get("/api/v1/scheduled_statuses?limit=2&max_id=#{scheduled_activity_id4}") - -      result = json_response(conn_res, 200) -      assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result -    end - -    test "shows a scheduled activity", %{conn: conn} do -      user = insert(:user) -      scheduled_activity = insert(:scheduled_activity, user: user) - -      res_conn = -        conn -        |> assign(:user, user) -        |> get("/api/v1/scheduled_statuses/#{scheduled_activity.id}") - -      assert %{"id" => scheduled_activity_id} = json_response(res_conn, 200) -      assert scheduled_activity_id == scheduled_activity.id |> to_string() - -      res_conn = -        conn -        |> assign(:user, user) -        |> get("/api/v1/scheduled_statuses/404") - -      assert %{"error" => "Record not found"} = json_response(res_conn, 404) -    end - -    test "updates a scheduled activity", %{conn: conn} do -      user = insert(:user) -      scheduled_activity = insert(:scheduled_activity, user: user) - -      new_scheduled_at = -        NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond) - -      res_conn = -        conn -        |> assign(:user, user) -        |> put("/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{ -          scheduled_at: new_scheduled_at -        }) - -      assert %{"scheduled_at" => expected_scheduled_at} = json_response(res_conn, 200) -      assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(new_scheduled_at) - -      res_conn = -        conn -        |> assign(:user, user) -        |> put("/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at}) - -      assert %{"error" => "Record not found"} = json_response(res_conn, 404) -    end - -    test "deletes a scheduled activity", %{conn: conn} do -      user = insert(:user) -      scheduled_activity = insert(:scheduled_activity, user: user) - -      res_conn = -        conn -        |> assign(:user, user) -        |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}") - -      assert %{} = json_response(res_conn, 200) -      assert nil == Repo.get(ScheduledActivity, scheduled_activity.id) - -      res_conn = -        conn -        |> assign(:user, user) -        |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}") - -      assert %{"error" => "Record not found"} = json_response(res_conn, 404) -    end -  end - -  test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do -    user1 = insert(:user) -    user2 = insert(:user) -    user3 = insert(:user) - -    {:ok, replied_to} = CommonAPI.post(user1, %{"status" => "cofe"}) - -    # Reply to status from another user -    conn1 = -      conn -      |> assign(:user, user2) -      |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id}) - -    assert %{"content" => "xD", "id" => id} = json_response(conn1, 200) - -    activity = Activity.get_by_id_with_object(id) - -    assert Object.normalize(activity).data["inReplyTo"] == Object.normalize(replied_to).data["id"] -    assert Activity.get_in_reply_to_activity(activity).id == replied_to.id - -    # Reblog from the third user -    conn2 = -      conn -      |> assign(:user, user3) -      |> post("/api/v1/statuses/#{activity.id}/reblog") - -    assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} = -             json_response(conn2, 200) - -    assert to_string(activity.id) == id - -    # Getting third user status -    conn3 = -      conn -      |> assign(:user, user3) -      |> get("api/v1/timelines/home") - -    [reblogged_activity] = json_response(conn3, 200) - -    assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id - -    replied_to_user = User.get_by_ap_id(replied_to.data["actor"]) -    assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id -  end - -  describe "create account by app" do      test "Account registration via Application", %{conn: conn} do        conn =          conn @@ -2900,6 +1668,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do            username: "lain",            email: "lain@example.org",            password: "PlzDontHackLain", +          bio: "Test Bio",            agreement: true          }) @@ -2918,6 +1687,18 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do        assert token_from_db.user.info.confirmation_pending      end +    test "returns error when user already registred", %{conn: conn, valid_params: valid_params} do +      _user = insert(:user, email: "lain@example.org") +      app_token = insert(:oauth_token, user: nil) + +      conn = +        conn +        |> put_req_header("authorization", "Bearer " <> app_token.token) + +      res = post(conn, "/api/v1/accounts", valid_params) +      assert json_response(res, 400) == %{"error" => "{\"email\":[\"has already been taken\"]}"} +    end +      test "rate limit", %{conn: conn} do        app_token = insert(:oauth_token, user: nil) @@ -2961,6 +1742,41 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do        assert json_response(conn, :too_many_requests) == %{"error" => "Throttled"}      end + +    test "returns bad_request if missing required params", %{ +      conn: conn, +      valid_params: valid_params +    } do +      app_token = insert(:oauth_token, user: nil) + +      conn = +        conn +        |> put_req_header("authorization", "Bearer " <> app_token.token) + +      res = post(conn, "/api/v1/accounts", valid_params) +      assert json_response(res, 200) + +      [{127, 0, 0, 1}, {127, 0, 0, 2}, {127, 0, 0, 3}, {127, 0, 0, 4}] +      |> Stream.zip(valid_params) +      |> Enum.each(fn {ip, {attr, _}} -> +        res = +          conn +          |> Map.put(:remote_ip, ip) +          |> post("/api/v1/accounts", Map.delete(valid_params, attr)) +          |> json_response(400) + +        assert res == %{"error" => "Missing parameters"} +      end) +    end + +    test "returns forbidden if token is invalid", %{conn: conn, valid_params: valid_params} do +      conn = +        conn +        |> put_req_header("authorization", "Bearer " <> "invalid-token") + +      res = post(conn, "/api/v1/accounts", valid_params) +      assert json_response(res, 403) == %{"error" => "Invalid credentials"} +    end    end    describe "GET /api/v1/polls/:id" do @@ -3135,197 +1951,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      end    end -  describe "GET /api/v1/statuses/:id/favourited_by" do -    setup do -      user = insert(:user) -      {:ok, activity} = CommonAPI.post(user, %{"status" => "test"}) - -      conn = -        build_conn() -        |> assign(:user, user) - -      [conn: conn, activity: activity, user: user] -    end - -    test "returns users who have favorited the status", %{conn: conn, activity: activity} do -      other_user = insert(:user) -      {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) - -      response = -        conn -        |> get("/api/v1/statuses/#{activity.id}/favourited_by") -        |> json_response(:ok) - -      [%{"id" => id}] = response - -      assert id == other_user.id -    end - -    test "returns empty array when status has not been favorited yet", %{ -      conn: conn, -      activity: activity -    } do -      response = -        conn -        |> get("/api/v1/statuses/#{activity.id}/favourited_by") -        |> json_response(:ok) - -      assert Enum.empty?(response) -    end - -    test "does not return users who have favorited the status but are blocked", %{ -      conn: %{assigns: %{user: user}} = conn, -      activity: activity -    } do -      other_user = insert(:user) -      {:ok, user} = User.block(user, other_user) - -      {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) - -      response = -        conn -        |> assign(:user, user) -        |> get("/api/v1/statuses/#{activity.id}/favourited_by") -        |> json_response(:ok) - -      assert Enum.empty?(response) -    end - -    test "does not fail on an unauthenticated request", %{conn: conn, activity: activity} do -      other_user = insert(:user) -      {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) - -      response = -        conn -        |> assign(:user, nil) -        |> get("/api/v1/statuses/#{activity.id}/favourited_by") -        |> json_response(:ok) - -      [%{"id" => id}] = response -      assert id == other_user.id -    end - -    test "requires authentification for private posts", %{conn: conn, user: user} do -      other_user = insert(:user) - -      {:ok, activity} = -        CommonAPI.post(user, %{ -          "status" => "@#{other_user.nickname} wanna get some #cofe together?", -          "visibility" => "direct" -        }) - -      {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) - -      conn -      |> assign(:user, nil) -      |> get("/api/v1/statuses/#{activity.id}/favourited_by") -      |> json_response(404) - -      response = -        build_conn() -        |> assign(:user, other_user) -        |> get("/api/v1/statuses/#{activity.id}/favourited_by") -        |> json_response(200) - -      [%{"id" => id}] = response -      assert id == other_user.id -    end -  end - -  describe "GET /api/v1/statuses/:id/reblogged_by" do -    setup do -      user = insert(:user) -      {:ok, activity} = CommonAPI.post(user, %{"status" => "test"}) - -      conn = -        build_conn() -        |> assign(:user, user) - -      [conn: conn, activity: activity, user: user] -    end - -    test "returns users who have reblogged the status", %{conn: conn, activity: activity} do -      other_user = insert(:user) -      {:ok, _, _} = CommonAPI.repeat(activity.id, other_user) - -      response = -        conn -        |> get("/api/v1/statuses/#{activity.id}/reblogged_by") -        |> json_response(:ok) - -      [%{"id" => id}] = response - -      assert id == other_user.id -    end - -    test "returns empty array when status has not been reblogged yet", %{ -      conn: conn, -      activity: activity -    } do -      response = -        conn -        |> get("/api/v1/statuses/#{activity.id}/reblogged_by") -        |> json_response(:ok) - -      assert Enum.empty?(response) -    end - -    test "does not return users who have reblogged the status but are blocked", %{ -      conn: %{assigns: %{user: user}} = conn, -      activity: activity -    } do -      other_user = insert(:user) -      {:ok, user} = User.block(user, other_user) - -      {:ok, _, _} = CommonAPI.repeat(activity.id, other_user) - -      response = -        conn -        |> assign(:user, user) -        |> get("/api/v1/statuses/#{activity.id}/reblogged_by") -        |> json_response(:ok) - -      assert Enum.empty?(response) -    end - -    test "does not fail on an unauthenticated request", %{conn: conn, activity: activity} do -      other_user = insert(:user) -      {:ok, _, _} = CommonAPI.repeat(activity.id, other_user) - -      response = -        conn -        |> assign(:user, nil) -        |> get("/api/v1/statuses/#{activity.id}/reblogged_by") -        |> json_response(:ok) - -      [%{"id" => id}] = response -      assert id == other_user.id -    end - -    test "requires authentification for private posts", %{conn: conn, user: user} do -      other_user = insert(:user) - -      {:ok, activity} = -        CommonAPI.post(user, %{ -          "status" => "@#{other_user.nickname} wanna get some #cofe together?", -          "visibility" => "direct" -        }) - -      conn -      |> assign(:user, nil) -      |> get("/api/v1/statuses/#{activity.id}/reblogged_by") -      |> json_response(404) - -      response = -        build_conn() -        |> assign(:user, other_user) -        |> get("/api/v1/statuses/#{activity.id}/reblogged_by") -        |> json_response(200) - -      assert [] == response -    end -  end -    describe "POST /auth/password, with valid parameters" do      setup %{conn: conn} do        user = insert(:user) @@ -3494,4 +2119,115 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do               ]      end    end + +  describe "PUT /api/v1/media/:id" do +    setup do +      actor = insert(:user) + +      file = %Plug.Upload{ +        content_type: "image/jpg", +        path: Path.absname("test/fixtures/image.jpg"), +        filename: "an_image.jpg" +      } + +      {:ok, %Object{} = object} = +        ActivityPub.upload( +          file, +          actor: User.ap_id(actor), +          description: "test-m" +        ) + +      [actor: actor, object: object] +    end + +    test "updates name of media", %{conn: conn, actor: actor, object: object} do +      media = +        conn +        |> assign(:user, actor) +        |> put("/api/v1/media/#{object.id}", %{"description" => "test-media"}) +        |> json_response(:ok) + +      assert media["description"] == "test-media" +      assert refresh_record(object).data["name"] == "test-media" +    end + +    test "returns error wheb request is bad", %{conn: conn, actor: actor, object: object} do +      media = +        conn +        |> assign(:user, actor) +        |> put("/api/v1/media/#{object.id}", %{}) +        |> json_response(400) + +      assert media == %{"error" => "bad_request"} +    end +  end + +  describe "DELETE /auth/sign_out" do +    test "redirect to root page", %{conn: conn} do +      user = insert(:user) + +      conn = +        conn +        |> assign(:user, user) +        |> delete("/auth/sign_out") + +      assert conn.status == 302 +      assert redirected_to(conn) == "/" +    end +  end + +  describe "GET /api/v1/accounts/:id/lists - account_lists" do +    test "returns lists to which the account belongs", %{conn: conn} do +      user = insert(:user) +      other_user = insert(:user) +      assert {:ok, %Pleroma.List{} = list} = Pleroma.List.create("Test List", user) +      {:ok, %{following: _following}} = Pleroma.List.follow(list, other_user) + +      res = +        conn +        |> assign(:user, user) +        |> get("/api/v1/accounts/#{other_user.id}/lists") +        |> json_response(200) + +      assert res == [%{"id" => to_string(list.id), "title" => "Test List"}] +    end +  end + +  describe "empty_array, stubs for mastodon api" do +    test "GET /api/v1/accounts/:id/identity_proofs", %{conn: conn} do +      user = insert(:user) + +      res = +        conn +        |> assign(:user, user) +        |> get("/api/v1/accounts/#{user.id}/identity_proofs") +        |> json_response(200) + +      assert res == [] +    end + +    test "GET /api/v1/endorsements", %{conn: conn} do +      user = insert(:user) + +      res = +        conn +        |> assign(:user, user) +        |> get("/api/v1/endorsements") +        |> json_response(200) + +      assert res == [] +    end + +    test "GET /api/v1/trends", %{conn: conn} do +      user = insert(:user) + +      res = +        conn +        |> assign(:user, user) +        |> get("/api/v1/trends") +        |> json_response(200) + +      assert res == [] +    end +  end  end diff --git a/test/web/mastodon_api/views/notification_view_test.exs b/test/web/mastodon_api/views/notification_view_test.exs index 9231aaec8..86268fcfa 100644 --- a/test/web/mastodon_api/views/notification_view_test.exs +++ b/test/web/mastodon_api/views/notification_view_test.exs @@ -28,7 +28,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do        pleroma: %{is_seen: false},        type: "mention",        account: AccountView.render("account.json", %{user: user, for: mentioned_user}), -      status: StatusView.render("status.json", %{activity: activity, for: mentioned_user}), +      status: StatusView.render("show.json", %{activity: activity, for: mentioned_user}),        created_at: Utils.to_masto_date(notification.inserted_at)      } @@ -51,7 +51,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do        pleroma: %{is_seen: false},        type: "favourite",        account: AccountView.render("account.json", %{user: another_user, for: user}), -      status: StatusView.render("status.json", %{activity: create_activity, for: user}), +      status: StatusView.render("show.json", %{activity: create_activity, for: user}),        created_at: Utils.to_masto_date(notification.inserted_at)      } @@ -73,7 +73,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do        pleroma: %{is_seen: false},        type: "reblog",        account: AccountView.render("account.json", %{user: another_user, for: user}), -      status: StatusView.render("status.json", %{activity: reblog_activity, for: user}), +      status: StatusView.render("show.json", %{activity: reblog_activity, for: user}),        created_at: Utils.to_masto_date(notification.inserted_at)      } diff --git a/test/web/mastodon_api/views/status_view_test.exs b/test/web/mastodon_api/views/status_view_test.exs index 51f8434fa..c17d0ef95 100644 --- a/test/web/mastodon_api/views/status_view_test.exs +++ b/test/web/mastodon_api/views/status_view_test.exs @@ -29,7 +29,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do      {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!", "visibility" => "direct"})      status = -      StatusView.render("status.json", +      StatusView.render("show.json",          activity: activity,          with_direct_conversation_id: true,          for: user @@ -46,7 +46,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do      Repo.delete(user)      Cachex.clear(:user_cache) -    %{account: ms_user} = StatusView.render("status.json", activity: activity) +    %{account: ms_user} = StatusView.render("show.json", activity: activity)      assert ms_user.acct == "erroruser@example.com"    end @@ -63,7 +63,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do      Cachex.clear(:user_cache) -    result = StatusView.render("status.json", activity: activity) +    result = StatusView.render("show.json", activity: activity)      assert result[:account][:id] == to_string(user.id)    end @@ -81,7 +81,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do      User.get_cached_by_ap_id(note.data["actor"]) -    status = StatusView.render("status.json", %{activity: note}) +    status = StatusView.render("show.json", %{activity: note})      assert status.content == ""    end @@ -93,7 +93,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do      convo_id = Utils.context_to_conversation_id(object_data["context"]) -    status = StatusView.render("status.json", %{activity: note}) +    status = StatusView.render("show.json", %{activity: note})      created_at =        (object_data["published"] || "") @@ -165,11 +165,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do      {:ok, user} = User.mute(user, other_user)      {:ok, activity} = CommonAPI.post(other_user, %{"status" => "test"}) -    status = StatusView.render("status.json", %{activity: activity}) +    status = StatusView.render("show.json", %{activity: activity})      assert status.muted == false -    status = StatusView.render("status.json", %{activity: activity, for: user}) +    status = StatusView.render("show.json", %{activity: activity, for: user})      assert status.muted == true    end @@ -181,13 +181,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do      {:ok, user} = User.mute(user, other_user)      {:ok, activity} = CommonAPI.post(other_user, %{"status" => "test"}) -    status = StatusView.render("status.json", %{activity: activity, for: user}) +    status = StatusView.render("show.json", %{activity: activity, for: user})      assert status.pleroma.thread_muted == false      {:ok, activity} = CommonAPI.add_mute(user, activity) -    status = StatusView.render("status.json", %{activity: activity, for: user}) +    status = StatusView.render("show.json", %{activity: activity, for: user})      assert status.pleroma.thread_muted == true    end @@ -196,11 +196,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do      user = insert(:user)      {:ok, activity} = CommonAPI.post(user, %{"status" => "Cute girls doing cute things"}) -    status = StatusView.render("status.json", %{activity: activity}) +    status = StatusView.render("show.json", %{activity: activity})      assert status.bookmarked == false -    status = StatusView.render("status.json", %{activity: activity, for: user}) +    status = StatusView.render("show.json", %{activity: activity, for: user})      assert status.bookmarked == false @@ -208,7 +208,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do      activity = Activity.get_by_id_with_object(activity.id) -    status = StatusView.render("status.json", %{activity: activity, for: user}) +    status = StatusView.render("show.json", %{activity: activity, for: user})      assert status.bookmarked == true    end @@ -220,7 +220,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do      {:ok, activity} =        CommonAPI.post(user, %{"status" => "he", "in_reply_to_status_id" => note.id}) -    status = StatusView.render("status.json", %{activity: activity}) +    status = StatusView.render("show.json", %{activity: activity})      assert status.in_reply_to_id == to_string(note.id) @@ -237,7 +237,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do      {:ok, [activity]} = OStatus.handle_incoming(incoming) -    status = StatusView.render("status.json", %{activity: activity}) +    status = StatusView.render("show.json", %{activity: activity})      assert status.mentions ==               Enum.map([user], fn u -> AccountView.render("mention.json", %{user: u}) end) @@ -263,7 +263,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do      assert length(activity.recipients) == 3 -    %{mentions: [mention] = mentions} = StatusView.render("status.json", %{activity: activity}) +    %{mentions: [mention] = mentions} = StatusView.render("show.json", %{activity: activity})      assert length(mentions) == 1      assert mention.url == recipient_ap_id @@ -300,7 +300,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do      assert length(activity.recipients) == 3 -    %{mentions: [mention] = mentions} = StatusView.render("status.json", %{activity: activity}) +    %{mentions: [mention] = mentions} = StatusView.render("show.json", %{activity: activity})      assert length(mentions) == 1      assert mention.url == recipient.ap_id @@ -340,7 +340,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do      id = "https://wedistribute.org/wp-json/pterotype/v1/object/85810"      [activity] = Activity.search(nil, id) -    status = StatusView.render("status.json", %{activity: activity}) +    status = StatusView.render("show.json", %{activity: activity})      assert status.uri == id      assert status.url == "https://wedistribute.org/2019/07/mastodon-drops-ostatus/" @@ -352,7 +352,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do      {:ok, reblog, _} = CommonAPI.repeat(activity.id, user) -    represented = StatusView.render("status.json", %{for: user, activity: reblog}) +    represented = StatusView.render("show.json", %{for: user, activity: reblog})      assert represented[:id] == to_string(reblog.id)      assert represented[:reblog][:id] == to_string(activity.id) @@ -369,7 +369,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do      %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"]) -    represented = StatusView.render("status.json", %{for: user, activity: activity}) +    represented = StatusView.render("show.json", %{for: user, activity: activity})      assert represented[:id] == to_string(activity.id)      assert length(represented[:media_attachments]) == 1 @@ -570,7 +570,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do          "status" => "drink more water"        }) -    result = StatusView.render("status.json", %{activity: activity, for: other_user}) +    result = StatusView.render("show.json", %{activity: activity, for: other_user})      assert result[:account][:pleroma][:relationship] ==               AccountView.render("relationship.json", %{user: other_user, target: user}) @@ -587,7 +587,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do      {:ok, activity, _object} = CommonAPI.repeat(activity.id, other_user) -    result = StatusView.render("status.json", %{activity: activity, for: user}) +    result = StatusView.render("show.json", %{activity: activity, for: user})      assert result[:account][:pleroma][:relationship] ==               AccountView.render("relationship.json", %{user: user, target: other_user}) @@ -604,7 +604,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do      {:ok, activity} =        CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"}) -    status = StatusView.render("status.json", activity: activity) +    status = StatusView.render("show.json", activity: activity)      assert status.visibility == "list"    end diff --git a/test/web/oauth/app_test.exs b/test/web/oauth/app_test.exs new file mode 100644 index 000000000..195b8c17f --- /dev/null +++ b/test/web/oauth/app_test.exs @@ -0,0 +1,33 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.OAuth.AppTest do +  use Pleroma.DataCase + +  alias Pleroma.Web.OAuth.App +  import Pleroma.Factory + +  describe "get_or_make/2" do +    test "gets exist app" do +      attrs = %{client_name: "Mastodon-Local", redirect_uris: "."} +      app = insert(:oauth_app, Map.merge(attrs, %{scopes: ["read", "write"]})) +      {:ok, %App{} = exist_app} = App.get_or_make(attrs, []) +      assert exist_app == app +    end + +    test "make app" do +      attrs = %{client_name: "Mastodon-Local", redirect_uris: "."} +      {:ok, %App{} = app} = App.get_or_make(attrs, ["write"]) +      assert app.scopes == ["write"] +    end + +    test "gets exist app and updates scopes" do +      attrs = %{client_name: "Mastodon-Local", redirect_uris: "."} +      app = insert(:oauth_app, Map.merge(attrs, %{scopes: ["read", "write"]})) +      {:ok, %App{} = exist_app} = App.get_or_make(attrs, ["read", "write", "follow", "push"]) +      assert exist_app.id == app.id +      assert exist_app.scopes == ["read", "write", "follow", "push"] +    end +  end +end  | 
