--- /dev/null
- 200 => Operation.response("Account", "application/json", Account)
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.AccountOperation do
+ alias OpenApiSpex.Operation
+ alias OpenApiSpex.Reference
+ alias OpenApiSpex.Schema
+ alias Pleroma.Web.ApiSpec.Schemas.Account
+ alias Pleroma.Web.ApiSpec.Schemas.AccountCreateRequest
+ alias Pleroma.Web.ApiSpec.Schemas.AccountCreateResponse
+ alias Pleroma.Web.ApiSpec.Schemas.AccountFollowsRequest
+ alias Pleroma.Web.ApiSpec.Schemas.AccountMuteRequest
+ alias Pleroma.Web.ApiSpec.Schemas.AccountRelationship
+ alias Pleroma.Web.ApiSpec.Schemas.AccountRelationshipsResponse
+ alias Pleroma.Web.ApiSpec.Schemas.AccountsResponse
+ alias Pleroma.Web.ApiSpec.Schemas.AccountUpdateCredentialsRequest
+ alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
+ alias Pleroma.Web.ApiSpec.Schemas.ListsResponse
+ alias Pleroma.Web.ApiSpec.Schemas.StatusesResponse
+ alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
+
+ import Pleroma.Web.ApiSpec.Helpers
+
+ @spec open_api_operation(atom) :: Operation.t()
+ def open_api_operation(action) do
+ operation = String.to_existing_atom("#{action}_operation")
+ apply(__MODULE__, operation, [])
+ end
+
+ @spec create_operation() :: Operation.t()
+ def create_operation do
+ %Operation{
+ tags: ["accounts"],
+ summary: "Register an account",
+ description:
+ "Creates a user and account records. Returns an account access token for the app that initiated the request. The app should save this token for later, and should wait for the user to confirm their account by clicking a link in their email inbox.",
+ operationId: "AccountController.create",
+ requestBody: request_body("Parameters", AccountCreateRequest, required: true),
+ responses: %{
+ 200 => Operation.response("Account", "application/json", AccountCreateResponse)
+ }
+ }
+ end
+
+ def verify_credentials_operation do
+ %Operation{
+ tags: ["accounts"],
+ description: "Test to make sure that the user token works.",
+ summary: "Verify account credentials",
+ operationId: "AccountController.verify_credentials",
+ security: [%{"oAuth" => ["read:accounts"]}],
+ responses: %{
+ 200 => Operation.response("Account", "application/json", Account)
+ }
+ }
+ end
+
+ def update_credentials_operation do
+ %Operation{
+ tags: ["accounts"],
+ summary: "Update account credentials",
+ description: "Update the user's display and preferences.",
+ operationId: "AccountController.update_credentials",
+ security: [%{"oAuth" => ["write:accounts"]}],
+ requestBody: request_body("Parameters", AccountUpdateCredentialsRequest, required: true),
+ responses: %{
+ 200 => Operation.response("Account", "application/json", Account)
+ }
+ }
+ end
+
+ def relationships_operation do
+ %Operation{
+ tags: ["accounts"],
+ summary: "Check relationships to other accounts",
+ operationId: "AccountController.relationships",
+ description: "Find out whether a given account is followed, blocked, muted, etc.",
+ security: [%{"oAuth" => ["read:follows"]}],
+ parameters: [
+ Operation.parameter(
+ :id,
+ :query,
+ %Schema{
+ oneOf: [%Schema{type: :array, items: %Schema{type: :string}}, %Schema{type: :string}]
+ },
+ "Account IDs",
+ example: "123"
+ )
+ ],
+ responses: %{
+ 200 => Operation.response("Account", "application/json", AccountRelationshipsResponse)
+ }
+ }
+ end
+
+ def show_operation do
+ %Operation{
+ tags: ["accounts"],
+ summary: "Account",
+ operationId: "AccountController.show",
+ description: "View information about a profile.",
+ parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
+ responses: %{
+ 200 => Operation.response("Account", "application/json", Account)
+ }
+ }
+ end
+
+ def statuses_operation do
+ %Operation{
+ tags: ["accounts"],
+ summary: "Statuses",
+ operationId: "AccountController.statuses",
+ description:
+ "Statuses posted to the given account. Public (for public statuses only), or user token + `read:statuses` (for private statuses the user is authorized to see)",
+ parameters:
+ [
+ %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
+ Operation.parameter(:pinned, :query, BooleanLike, "Include only pinned statuses"),
+ Operation.parameter(:tagged, :query, :string, "With tag"),
+ Operation.parameter(
+ :only_media,
+ :query,
+ BooleanLike,
+ "Include only statuses with media attached"
+ ),
+ Operation.parameter(
+ :with_muted,
+ :query,
+ BooleanLike,
+ "Include statuses from muted acccounts."
+ ),
+ Operation.parameter(:exclude_reblogs, :query, BooleanLike, "Exclude reblogs"),
+ Operation.parameter(
+ :exclude_visibilities,
+ :query,
+ %Schema{type: :array, items: VisibilityScope},
+ "Exclude visibilities"
+ )
+ ] ++ pagination_params(),
+ responses: %{
+ 200 => Operation.response("Statuses", "application/json", StatusesResponse)
+ }
+ }
+ end
+
+ def followers_operation do
+ %Operation{
+ tags: ["accounts"],
+ summary: "Followers",
+ operationId: "AccountController.followers",
+ security: [%{"oAuth" => ["read:accounts"]}],
+ description:
+ "Accounts which follow the given account, if network is not hidden by the account owner.",
+ parameters:
+ [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}] ++ pagination_params(),
+ responses: %{
+ 200 => Operation.response("Accounts", "application/json", AccountsResponse)
+ }
+ }
+ end
+
+ def following_operation do
+ %Operation{
+ tags: ["accounts"],
+ summary: "Following",
+ operationId: "AccountController.following",
+ security: [%{"oAuth" => ["read:accounts"]}],
+ description:
+ "Accounts which the given account is following, if network is not hidden by the account owner.",
+ parameters:
+ [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}] ++ pagination_params(),
+ responses: %{200 => Operation.response("Accounts", "application/json", AccountsResponse)}
+ }
+ end
+
+ def lists_operation do
+ %Operation{
+ tags: ["accounts"],
+ summary: "Lists containing this account",
+ operationId: "AccountController.lists",
+ security: [%{"oAuth" => ["read:lists"]}],
+ description: "User lists that you have added this account to.",
+ parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
+ responses: %{200 => Operation.response("Lists", "application/json", ListsResponse)}
+ }
+ end
+
+ def follow_operation do
+ %Operation{
+ tags: ["accounts"],
+ summary: "Follow",
+ operationId: "AccountController.follow",
+ security: [%{"oAuth" => ["follow", "write:follows"]}],
+ description: "Follow the given account",
+ parameters: [
+ %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
+ Operation.parameter(
+ :reblogs,
+ :query,
+ BooleanLike,
+ "Receive this account's reblogs in home timeline? Defaults to true."
+ )
+ ],
+ responses: %{
+ 200 => Operation.response("Relationship", "application/json", AccountRelationship)
+ }
+ }
+ end
+
+ def unfollow_operation do
+ %Operation{
+ tags: ["accounts"],
+ summary: "Unfollow",
+ operationId: "AccountController.unfollow",
+ security: [%{"oAuth" => ["follow", "write:follows"]}],
+ description: "Unfollow the given account",
+ parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
+ responses: %{
+ 200 => Operation.response("Relationship", "application/json", AccountRelationship)
+ }
+ }
+ end
+
+ def mute_operation do
+ %Operation{
+ tags: ["accounts"],
+ summary: "Mute",
+ operationId: "AccountController.mute",
+ security: [%{"oAuth" => ["follow", "write:mutes"]}],
+ requestBody: request_body("Parameters", AccountMuteRequest),
+ description:
+ "Mute the given account. Clients should filter statuses and notifications from this account, if received (e.g. due to a boost in the Home timeline).",
+ parameters: [
+ %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
+ Operation.parameter(
+ :notifications,
+ :query,
+ %Schema{allOf: [BooleanLike], default: true},
+ "Mute notifications in addition to statuses? Defaults to `true`."
+ )
+ ],
+ responses: %{
+ 200 => Operation.response("Relationship", "application/json", AccountRelationship)
+ }
+ }
+ end
+
+ def unmute_operation do
+ %Operation{
+ tags: ["accounts"],
+ summary: "Unmute",
+ operationId: "AccountController.unmute",
+ security: [%{"oAuth" => ["follow", "write:mutes"]}],
+ description: "Unmute the given account.",
+ parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
+ responses: %{
+ 200 => Operation.response("Relationship", "application/json", AccountRelationship)
+ }
+ }
+ end
+
+ def block_operation do
+ %Operation{
+ tags: ["accounts"],
+ summary: "Block",
+ operationId: "AccountController.block",
+ security: [%{"oAuth" => ["follow", "write:blocks"]}],
+ description:
+ "Block the given account. Clients should filter statuses from this account if received (e.g. due to a boost in the Home timeline)",
+ parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
+ responses: %{
+ 200 => Operation.response("Relationship", "application/json", AccountRelationship)
+ }
+ }
+ end
+
+ def unblock_operation do
+ %Operation{
+ tags: ["accounts"],
+ summary: "Unblock",
+ operationId: "AccountController.unblock",
+ security: [%{"oAuth" => ["follow", "write:blocks"]}],
+ description: "Unblock the given account.",
+ parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
+ responses: %{
+ 200 => Operation.response("Relationship", "application/json", AccountRelationship)
+ }
+ }
+ end
+
+ def follows_operation do
+ %Operation{
+ tags: ["accounts"],
+ summary: "Follows",
+ operationId: "AccountController.follows",
+ security: [%{"oAuth" => ["follow", "write:follows"]}],
+ requestBody: request_body("Parameters", AccountFollowsRequest, required: true),
+ responses: %{
++ 200 => Operation.response("Account", "application/json", AccountRelationship)
+ }
+ }
+ end
+
+ def mutes_operation do
+ %Operation{
+ tags: ["accounts"],
+ summary: "Muted accounts",
+ operationId: "AccountController.mutes",
+ description: "Accounts the user has muted.",
+ security: [%{"oAuth" => ["follow", "read:mutes"]}],
+ responses: %{
+ 200 => Operation.response("Accounts", "application/json", AccountsResponse)
+ }
+ }
+ end
+
+ def blocks_operation do
+ %Operation{
+ tags: ["accounts"],
+ summary: "Blocked users",
+ operationId: "AccountController.blocks",
+ description: "View your blocks. See also accounts/:id/{block,unblock}",
+ security: [%{"oAuth" => ["read:blocks"]}],
+ responses: %{
+ 200 => Operation.response("Accounts", "application/json", AccountsResponse)
+ }
+ }
+ end
+
+ def endorsements_operation do
+ %Operation{
+ tags: ["accounts"],
+ summary: "Endorsements",
+ operationId: "AccountController.endorsements",
+ description: "Not implemented",
+ security: [%{"oAuth" => ["read:accounts"]}],
+ responses: %{
+ 200 => Operation.response("Empry array", "application/json", %Schema{type: :array})
+ }
+ }
+ end
+
+ def identity_proofs_operation do
+ %Operation{
+ tags: ["accounts"],
+ summary: "Identity proofs",
+ operationId: "AccountController.identity_proofs",
+ description: "Not implemented",
+ responses: %{
+ 200 => Operation.response("Empry array", "application/json", %Schema{type: :array})
+ }
+ }
+ end
+end
plug(RateLimiter, [name: :app_account_creation] when action == :create)
plug(:assign_account_by_id when action in @needs_account)
+ plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError)
+
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
+ defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.AccountOperation
+
@doc "POST /api/v1/accounts"
- def create(
- %{assigns: %{app: app}} = conn,
- %{"username" => nickname, "password" => _, "agreement" => true} = params
- ) do
+ def create(%{assigns: %{app: app}, body_params: params} = conn, _params) do
params =
params
|> Map.take([
- "email",
- "captcha_solution",
- "captcha_token",
- "captcha_answer_data",
- "token",
- "password"
+ :email,
+ :bio,
+ :captcha_solution,
+ :captcha_token,
+ :captcha_answer_data,
+ :token,
+ :password,
+ :fullname
])
- |> Map.put("nickname", nickname)
- |> Map.put("fullname", params["fullname"] || nickname)
- |> Map.put("bio", params["bio"] || "")
- |> Map.put("confirm", params["password"])
- |> Map.put("trusted_app", app.trusted)
+ |> Map.put(:nickname, params.username)
+ |> Map.put(:fullname, params.fullname || params.username)
+ |> Map.put(:bio, params.bio || "")
+ |> Map.put(:confirm, params.password)
+ |> Map.put(:trusted_app, app.trusted)
with :ok <- validate_email_param(params),
{:ok, user} <- TwitterAPI.register_user(params, need_confirmation: true),
render_error(conn, :forbidden, "Invalid credentials")
end
- defp validate_email_param(%{"email" => _}), do: :ok
+ defp validate_email_param(%{:email => email}) when not is_nil(email), do: :ok
defp validate_email_param(_) do
case Pleroma.Config.get([:instance, :account_activation_required]) do
end
@doc "PATCH /api/v1/accounts/update_credentials"
- def update_credentials(%{assigns: %{user: user}} = conn, params) do
+ def update_credentials(%{assigns: %{user: original_user}, body_params: params} = conn, _params) do
+ user = original_user
+
+ params =
+ params
+ |> Map.from_struct()
+ |> Enum.filter(fn {_, value} -> not is_nil(value) end)
+ |> Enum.into(%{})
+
user_params =
[
:no_rich_text,
:discoverable
]
|> Enum.reduce(%{}, fn key, acc ->
- add_if_present(acc, params, to_string(key), key, &{:ok, truthy_param?(&1)})
+ add_if_present(acc, params, key, key, &{:ok, truthy_param?(&1)})
end)
- |> add_if_present(params, "display_name", :name)
- |> add_if_present(params, "note", :bio)
- |> add_if_present(params, "avatar", :avatar)
- |> add_if_present(params, "header", :banner)
- |> add_if_present(params, "pleroma_background_image", :background)
+ |> add_if_present(params, :display_name, :name)
+ |> add_if_present(params, :note, :bio)
+ |> add_if_present(params, :avatar, :avatar)
+ |> add_if_present(params, :header, :banner)
+ |> add_if_present(params, :pleroma_background_image, :background)
|> add_if_present(
params,
- "fields_attributes",
+ :fields_attributes,
:raw_fields,
&{:ok, normalize_fields_attributes(&1)}
)
- |> add_if_present(params, "pleroma_settings_store", :pleroma_settings_store)
- |> add_if_present(params, "default_scope", :default_scope)
- |> add_if_present(params, "actor_type", :actor_type)
+ |> add_if_present(params, :pleroma_settings_store, :pleroma_settings_store)
+ |> add_if_present(params, :default_scope, :default_scope)
+ |> add_if_present(params, :actor_type, :actor_type)
changeset = User.update_changeset(user, user_params)
defp add_if_present(map, params, params_field, map_field, value_function \\ &{:ok, &1}) do
with true <- Map.has_key?(params, params_field),
- {:ok, new_value} <- value_function.(params[params_field]) do
+ {:ok, new_value} <- value_function.(Map.get(params, params_field)) do
Map.put(map, map_field, new_value)
else
_ -> map
if Enum.all?(fields, &is_tuple/1) do
Enum.map(fields, fn {_, v} -> v end)
else
- fields
+ Enum.map(fields, fn
+ %Pleroma.Web.ApiSpec.Schemas.AccountAttributeField{} = field ->
+ %{"name" => field.name, "value" => field.value}
+
+ field ->
+ field
+ end)
end
end
@doc "GET /api/v1/accounts/relationships"
- def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+ def relationships(%{assigns: %{user: user}} = conn, %{id: id}) do
targets = User.get_all_by_ids(List.wrap(id))
render(conn, "relationships.json", user: user, targets: targets)
def relationships(%{assigns: %{user: _user}} = conn, _), do: json(conn, [])
@doc "GET /api/v1/accounts/:id"
- def show(%{assigns: %{user: for_user}} = conn, %{"id" => nickname_or_id}) do
+ def show(%{assigns: %{user: for_user}} = conn, %{id: nickname_or_id}) do
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id, for: for_user),
true <- User.visible_for?(user, for_user) do
render(conn, "show.json", user: user, for: for_user)
@doc "GET /api/v1/accounts/:id/statuses"
def statuses(%{assigns: %{user: reading_user}} = conn, params) do
- with %User{} = user <- User.get_cached_by_nickname_or_id(params["id"], for: reading_user),
+ with %User{} = user <- User.get_cached_by_nickname_or_id(params.id, for: reading_user),
true <- User.visible_for?(user, reading_user) do
params =
params
- |> Map.put("tag", params["tagged"])
- |> Map.delete("godmode")
+ |> Map.delete(:tagged)
+ |> Enum.filter(&(not is_nil(&1)))
+ |> Map.new(fn {key, value} -> {to_string(key), value} end)
+ |> Map.put("tag", params[:tagged])
activities = ActivityPub.fetch_user_activities(user, reading_user, params)
@doc "GET /api/v1/accounts/:id/followers"
def followers(%{assigns: %{user: for_user, account: user}} = conn, params) do
+ params =
+ params
+ |> Enum.map(fn {key, value} -> {to_string(key), value} end)
+ |> Enum.into(%{})
+
followers =
cond do
for_user && user.id == for_user.id -> MastodonAPI.get_followers(user, params)
@doc "GET /api/v1/accounts/:id/following"
def following(%{assigns: %{user: for_user, account: user}} = conn, params) do
+ params =
+ params
+ |> Enum.map(fn {key, value} -> {to_string(key), value} end)
+ |> Enum.into(%{})
+
followers =
cond do
for_user && user.id == for_user.id -> MastodonAPI.get_friends(user, params)
@doc "POST /api/v1/accounts/:id/follow"
def follow(%{assigns: %{user: %{id: id}, account: %{id: id}}}, _params) do
- {:error, :not_found}
+ {:error, "Can not follow yourself"}
end
- def follow(%{assigns: %{user: follower, account: followed}} = conn, _params) do
- with {:ok, follower} <- MastodonAPI.follow(follower, followed, conn.params) do
+ def follow(%{assigns: %{user: follower, account: followed}} = conn, params) do
+ with {:ok, follower} <- MastodonAPI.follow(follower, followed, params) do
render(conn, "relationship.json", user: follower, target: followed)
else
{:error, message} -> json_response(conn, :forbidden, %{error: message})
@doc "POST /api/v1/accounts/:id/unfollow"
def unfollow(%{assigns: %{user: %{id: id}, account: %{id: id}}}, _params) do
- {:error, :not_found}
+ {:error, "Can not unfollow yourself"}
end
def unfollow(%{assigns: %{user: follower, account: followed}} = conn, _params) do
end
@doc "POST /api/v1/accounts/:id/mute"
- def mute(%{assigns: %{user: muter, account: muted}} = conn, params) do
- notifications? = params |> Map.get("notifications", true) |> truthy_param?()
-
- with {:ok, _user_relationships} <- User.mute(muter, muted, notifications?) do
+ def mute(%{assigns: %{user: muter, account: muted}, body_params: params} = conn, _params) do
+ with {:ok, _user_relationships} <- User.mute(muter, muted, params.notifications) do
render(conn, "relationship.json", user: muter, target: muted)
else
{:error, message} -> json_response(conn, :forbidden, %{error: message})
end
@doc "POST /api/v1/follows"
- def follows(%{assigns: %{user: follower}, body_params: %{uri: uri}} = conn, _) do
- with {_, %User{} = followed} <- {:followed, User.get_cached_by_nickname(uri)},
- {_, true} <- {:followed, follower.id != followed.id},
- {:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do
- render(conn, "show.json", user: followed, for: follower)
- else
- {:followed, _} -> {:error, :not_found}
- {:error, message} -> json_response(conn, :forbidden, %{error: message})
- def follows(conn, %{"uri" => uri}) do
++ def follows(%{body_params: %{uri: uri}} = conn, _) do
+ case User.get_cached_by_nickname(uri) do
+ %User{} = user ->
+ conn
+ |> assign(:account, user)
+ |> follow(%{})
+
+ nil ->
+ {:error, :not_found}
end
end
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.InternalFetchActor
+ alias Pleroma.Web.ApiSpec
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.OAuth.Token
+ import OpenApiSpex.TestAssertions
import Pleroma.Factory
describe "account fetching" do
{:ok, activity} = CommonAPI.post(user_two, %{"status" => "User one sux0rz"})
{:ok, repeat, _} = CommonAPI.repeat(activity.id, user_three)
- resp = get(conn, "/api/v1/accounts/#{user_two.id}/statuses")
-
- assert [%{"id" => id}] = json_response(resp, 200)
+ assert resp = get(conn, "/api/v1/accounts/#{user_two.id}/statuses") |> json_response(200)
+ assert [%{"id" => id}] = resp
+ assert_schema(resp, "StatusesResponse", ApiSpec.spec())
assert id == activity.id
# Even a blocked user will deliver the full user timeline, there would be
# no point in looking at a blocked users timeline otherwise
- resp = get(conn, "/api/v1/accounts/#{user_two.id}/statuses")
-
- assert [%{"id" => id}] = json_response(resp, 200)
+ assert resp = get(conn, "/api/v1/accounts/#{user_two.id}/statuses") |> json_response(200)
+ assert [%{"id" => id}] = resp
assert id == activity.id
+ assert_schema(resp, "StatusesResponse", ApiSpec.spec())
# Third user's timeline includes the repeat when viewed by unauthenticated user
- resp = get(build_conn(), "/api/v1/accounts/#{user_three.id}/statuses")
- assert [%{"id" => id}] = json_response(resp, 200)
+ resp = get(build_conn(), "/api/v1/accounts/#{user_three.id}/statuses") |> json_response(200)
+ assert [%{"id" => id}] = resp
assert id == repeat.id
+ assert_schema(resp, "StatusesResponse", ApiSpec.spec())
# When viewing a third user's timeline, the blocked users' statuses will NOT be shown
resp = get(conn, "/api/v1/accounts/#{user_three.id}/statuses")
{:ok, private_activity} =
CommonAPI.post(user_one, %{"status" => "private", "visibility" => "private"})
- resp = get(conn, "/api/v1/accounts/#{user_one.id}/statuses")
-
- assert [%{"id" => id}] = json_response(resp, 200)
+ resp = get(conn, "/api/v1/accounts/#{user_one.id}/statuses") |> json_response(200)
+ assert [%{"id" => id}] = resp
assert id == to_string(activity.id)
+ assert_schema(resp, "StatusesResponse", ApiSpec.spec())
resp =
conn
|> assign(:user, user_two)
|> assign(:token, insert(:oauth_token, user: user_two, scopes: ["read:statuses"]))
|> get("/api/v1/accounts/#{user_one.id}/statuses")
+ |> json_response(200)
- assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200)
+ assert [%{"id" => id_one}, %{"id" => id_two}] = resp
assert id_one == to_string(direct_activity.id)
assert id_two == to_string(activity.id)
+ assert_schema(resp, "StatusesResponse", ApiSpec.spec())
resp =
conn
|> assign(:user, user_three)
|> assign(:token, insert(:oauth_token, user: user_three, scopes: ["read:statuses"]))
|> get("/api/v1/accounts/#{user_one.id}/statuses")
+ |> json_response(200)
- assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200)
+ assert [%{"id" => id_one}, %{"id" => id_two}] = resp
assert id_one == to_string(private_activity.id)
assert id_two == to_string(activity.id)
+ assert_schema(resp, "StatusesResponse", ApiSpec.spec())
end
test "unimplemented pinned statuses feature", %{conn: conn} do
{:ok, image_post} = CommonAPI.post(user, %{"status" => "cofe", "media_ids" => [media_id]})
- conn = get(conn, "/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "true"})
+ conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?only_media=true")
assert [%{"id" => id}] = json_response(conn, 200)
assert id == to_string(image_post.id)
+ assert_schema(json_response(conn, 200), "StatusesResponse", ApiSpec.spec())
- conn = get(build_conn(), "/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "1"})
+ conn = get(build_conn(), "/api/v1/accounts/#{user.id}/statuses?only_media=1")
assert [%{"id" => id}] = json_response(conn, 200)
assert id == to_string(image_post.id)
+ assert_schema(json_response(conn, 200), "StatusesResponse", ApiSpec.spec())
end
test "gets a user's statuses without reblogs", %{user: user, conn: conn} do
{:ok, post} = CommonAPI.post(user, %{"status" => "HI!!!"})
{:ok, _, _} = CommonAPI.repeat(post.id, user)
- conn = get(conn, "/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "true"})
+ conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?exclude_reblogs=true")
assert [%{"id" => id}] = json_response(conn, 200)
assert id == to_string(post.id)
+ assert_schema(json_response(conn, 200), "StatusesResponse", ApiSpec.spec())
- conn = get(conn, "/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "1"})
+ conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?exclude_reblogs=1")
assert [%{"id" => id}] = json_response(conn, 200)
assert id == to_string(post.id)
+ assert_schema(json_response(conn, 200), "StatusesResponse", ApiSpec.spec())
end
test "filters user's statuses by a hashtag", %{user: user, conn: conn} do
{:ok, post} = CommonAPI.post(user, %{"status" => "#hashtag"})
{:ok, _post} = CommonAPI.post(user, %{"status" => "hashtag"})
- conn = get(conn, "/api/v1/accounts/#{user.id}/statuses", %{"tagged" => "hashtag"})
+ conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?tagged=hashtag")
assert [%{"id" => id}] = json_response(conn, 200)
assert id == to_string(post.id)
+ assert_schema(json_response(conn, 200), "StatusesResponse", ApiSpec.spec())
end
test "the user views their own timelines and excludes direct messages", %{
{:ok, public_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "public"})
{:ok, _direct_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"})
- conn =
- get(conn, "/api/v1/accounts/#{user.id}/statuses", %{"exclude_visibilities" => ["direct"]})
+ conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?exclude_visibilities[]=direct")
assert [%{"id" => id}] = json_response(conn, 200)
assert id == to_string(public_activity.id)
+ assert_schema(json_response(conn, 200), "StatusesResponse", ApiSpec.spec())
end
end
res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses")
assert length(json_response(res_conn, 200)) == 1
+ assert_schema(json_response(res_conn, 200), "StatusesResponse", ApiSpec.spec())
res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses")
assert length(json_response(res_conn, 200)) == 1
+ assert_schema(json_response(res_conn, 200), "StatusesResponse", ApiSpec.spec())
end
end
res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses")
assert length(json_response(res_conn, 200)) == 1
+ assert_schema(json_response(res_conn, 200), "StatusesResponse", ApiSpec.spec())
end
test "if user is authenticated", %{local: local, remote: remote} do
res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses")
assert length(json_response(res_conn, 200)) == 1
+ assert_schema(json_response(res_conn, 200), "StatusesResponse", ApiSpec.spec())
res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses")
assert length(json_response(res_conn, 200)) == 1
+ assert_schema(json_response(res_conn, 200), "StatusesResponse", ApiSpec.spec())
end
end
test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses")
assert length(json_response(res_conn, 200)) == 1
+ assert_schema(json_response(res_conn, 200), "StatusesResponse", ApiSpec.spec())
res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses")
res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses")
assert length(json_response(res_conn, 200)) == 1
+ assert_schema(json_response(res_conn, 200), "StatusesResponse", ApiSpec.spec())
res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses")
assert length(json_response(res_conn, 200)) == 1
+ assert_schema(json_response(res_conn, 200), "StatusesResponse", ApiSpec.spec())
end
end
assert [%{"id" => id}] = json_response(conn, 200)
assert id == to_string(user.id)
+ assert_schema(json_response(conn, 200), "AccountsResponse", ApiSpec.spec())
end
test "getting followers, hide_followers", %{user: user, conn: conn} do
|> get("/api/v1/accounts/#{other_user.id}/followers")
refute [] == json_response(conn, 200)
+ assert_schema(json_response(conn, 200), "AccountsResponse", ApiSpec.spec())
end
test "getting followers, pagination", %{user: user, conn: conn} do
assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200)
assert id3 == follower3.id
assert id2 == follower2.id
+ assert_schema(json_response(res_conn, 200), "AccountsResponse", ApiSpec.spec())
res_conn = get(conn, "/api/v1/accounts/#{user.id}/followers?max_id=#{follower3.id}")
assert [link_header] = get_resp_header(res_conn, "link")
assert link_header =~ ~r/min_id=#{follower2.id}/
assert link_header =~ ~r/max_id=#{follower2.id}/
+ assert_schema(json_response(res_conn, 200), "AccountsResponse", ApiSpec.spec())
end
end
assert [%{"id" => id}] = json_response(conn, 200)
assert id == to_string(other_user.id)
+ assert_schema(json_response(conn, 200), "AccountsResponse", ApiSpec.spec())
end
test "getting following, hide_follows, other user requesting" do
|> get("/api/v1/accounts/#{user.id}/following")
assert [] == json_response(conn, 200)
+ assert_schema(json_response(conn, 200), "AccountsResponse", ApiSpec.spec())
end
test "getting following, hide_follows, same user requesting" do
assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200)
assert id3 == following3.id
assert id2 == following2.id
+ assert_schema(json_response(res_conn, 200), "AccountsResponse", ApiSpec.spec())
res_conn = get(conn, "/api/v1/accounts/#{user.id}/following?max_id=#{following3.id}")
assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200)
assert id2 == following2.id
assert id1 == following1.id
+ assert_schema(json_response(res_conn, 200), "AccountsResponse", ApiSpec.spec())
res_conn =
get(conn, "/api/v1/accounts/#{user.id}/following?limit=1&max_id=#{following3.id}")
assert [link_header] = get_resp_header(res_conn, "link")
assert link_header =~ ~r/min_id=#{following2.id}/
assert link_header =~ ~r/max_id=#{following2.id}/
+ assert_schema(json_response(res_conn, 200), "AccountsResponse", ApiSpec.spec())
end
end
ret_conn = post(conn, "/api/v1/accounts/#{other_user.id}/follow")
assert %{"id" => _id, "following" => true} = json_response(ret_conn, 200)
+ assert_schema(json_response(ret_conn, 200), "AccountRelationship", ApiSpec.spec())
ret_conn = post(conn, "/api/v1/accounts/#{other_user.id}/unfollow")
assert %{"id" => _id, "following" => false} = json_response(ret_conn, 200)
+ assert_schema(json_response(ret_conn, 200), "AccountRelationship", ApiSpec.spec())
- conn = post(conn, "/api/v1/follows", %{"uri" => other_user.nickname})
+ conn =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/follows", %{"uri" => other_user.nickname})
assert %{"id" => id} = json_response(conn, 200)
assert id == to_string(other_user.id)
+ assert_schema(json_response(conn, 200), "Account", ApiSpec.spec())
end
test "cancelling follow request", %{conn: conn} do
%{id: other_user_id} = insert(:user, %{locked: true})
- assert %{"id" => ^other_user_id, "following" => false, "requested" => true} =
- conn |> post("/api/v1/accounts/#{other_user_id}/follow") |> json_response(:ok)
+ resp = conn |> post("/api/v1/accounts/#{other_user_id}/follow") |> json_response(:ok)
+
+ assert %{"id" => ^other_user_id, "following" => false, "requested" => true} = resp
+ assert_schema(resp, "AccountRelationship", ApiSpec.spec())
+
+ resp = conn |> post("/api/v1/accounts/#{other_user_id}/unfollow") |> json_response(:ok)
- assert %{"id" => ^other_user_id, "following" => false, "requested" => false} =
- conn |> post("/api/v1/accounts/#{other_user_id}/unfollow") |> json_response(:ok)
+ assert %{"id" => ^other_user_id, "following" => false, "requested" => false} = resp
+ assert_schema(resp, "AccountRelationship", ApiSpec.spec())
end
test "following without reblogs" do
ret_conn = post(conn, "/api/v1/accounts/#{followed.id}/follow?reblogs=false")
assert %{"showing_reblogs" => false} = json_response(ret_conn, 200)
+ assert_schema(json_response(ret_conn, 200), "AccountRelationship", ApiSpec.spec())
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "hey"})
{:ok, reblog, _} = CommonAPI.repeat(activity.id, followed)
ret_conn = post(conn, "/api/v1/accounts/#{followed.id}/follow?reblogs=true")
assert %{"showing_reblogs" => true} = json_response(ret_conn, 200)
+ assert_schema(json_response(ret_conn, 200), "AccountRelationship", ApiSpec.spec())
conn = get(conn, "/api/v1/timelines/home")
test "following / unfollowing errors", %{user: user, conn: conn} do
# self follow
conn_res = post(conn, "/api/v1/accounts/#{user.id}/follow")
- assert %{"error" => "Record not found"} = json_response(conn_res, 404)
+ assert %{"error" => "Can not follow yourself"} = json_response(conn_res, 400)
# self unfollow
user = User.get_cached_by_id(user.id)
conn_res = post(conn, "/api/v1/accounts/#{user.id}/unfollow")
- assert %{"error" => "Record not found"} = json_response(conn_res, 404)
+ assert %{"error" => "Can not unfollow yourself"} = json_response(conn_res, 400)
# self follow via uri
user = User.get_cached_by_id(user.id)
- conn_res = post(conn, "/api/v1/follows", %{"uri" => user.nickname})
+
+ conn_res =
+ conn
+ |> put_req_header("content-type", "multipart/form-data")
+ |> post("/api/v1/follows", %{"uri" => user.nickname})
+
- assert %{"error" => "Record not found"} = json_response(conn_res, 404)
+ assert %{"error" => "Can not follow yourself"} = json_response(conn_res, 400)
# follow non existing user
conn_res = post(conn, "/api/v1/accounts/doesntexist/follow")
assert %{"error" => "Record not found"} = json_response(conn_res, 404)
# follow non existing user via uri
- conn_res = post(conn, "/api/v1/follows", %{"uri" => "doesntexist"})
+ conn_res =
+ conn
+ |> put_req_header("content-type", "multipart/form-data")
+ |> post("/api/v1/follows", %{"uri" => "doesntexist"})
+
assert %{"error" => "Record not found"} = json_response(conn_res, 404)
# unfollow non existing user
test "with notifications", %{conn: conn} do
other_user = insert(:user)
- ret_conn = post(conn, "/api/v1/accounts/#{other_user.id}/mute")
+ ret_conn =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/accounts/#{other_user.id}/mute")
response = json_response(ret_conn, 200)
assert %{"id" => _id, "muting" => true, "muting_notifications" => true} = response
+ assert_schema(response, "AccountRelationship", ApiSpec.spec())
conn = post(conn, "/api/v1/accounts/#{other_user.id}/unmute")
response = json_response(conn, 200)
assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response
+ assert_schema(response, "AccountRelationship", ApiSpec.spec())
end
test "without notifications", %{conn: conn} do
other_user = insert(:user)
ret_conn =
- post(conn, "/api/v1/accounts/#{other_user.id}/mute", %{"notifications" => "false"})
+ conn
+ |> put_req_header("content-type", "multipart/form-data")
+ |> post("/api/v1/accounts/#{other_user.id}/mute", %{"notifications" => "false"})
response = json_response(ret_conn, 200)
assert %{"id" => _id, "muting" => true, "muting_notifications" => false} = response
+ assert_schema(response, "AccountRelationship", ApiSpec.spec())
conn = post(conn, "/api/v1/accounts/#{other_user.id}/unmute")
response = json_response(conn, 200)
assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response
+ assert_schema(response, "AccountRelationship", ApiSpec.spec())
end
end
ret_conn = post(conn, "/api/v1/accounts/#{other_user.id}/block")
assert %{"id" => _id, "blocking" => true} = json_response(ret_conn, 200)
+ assert_schema(json_response(ret_conn, 200), "AccountRelationship", ApiSpec.spec())
conn = post(conn, "/api/v1/accounts/#{other_user.id}/unblock")
assert %{"id" => _id, "blocking" => false} = json_response(conn, 200)
+ assert_schema(json_response(ret_conn, 200), "AccountRelationship", ApiSpec.spec())
end
describe "create account by app" do
conn =
build_conn()
+ |> put_req_header("content-type", "multipart/form-data")
|> put_req_header("authorization", "Bearer " <> token)
|> post("/api/v1/accounts", %{
username: "lain",
_user = insert(:user, email: "lain@example.org")
app_token = insert(:oauth_token, user: nil)
- conn =
+ res =
conn
|> put_req_header("authorization", "Bearer " <> app_token.token)
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/accounts", valid_params)
- res = post(conn, "/api/v1/accounts", valid_params)
assert json_response(res, 400) == %{"error" => "{\"email\":[\"has already been taken\"]}"}
end
} do
app_token = insert(:oauth_token, user: nil)
- conn = put_req_header(conn, "authorization", "Bearer " <> app_token.token)
+ conn =
+ conn
+ |> put_req_header("authorization", "Bearer " <> app_token.token)
+ |> put_req_header("content-type", "application/json")
res = post(conn, "/api/v1/accounts", valid_params)
assert json_response(res, 200)
|> post("/api/v1/accounts", Map.delete(valid_params, attr))
|> json_response(400)
- assert res == %{"error" => "Missing parameters"}
+ assert res == %{
+ "error" => "Missing field: #{attr}.",
+ "errors" => [
+ %{
+ "message" => "Missing field: #{attr}",
+ "source" => %{"pointer" => "/#{attr}"},
+ "title" => "Invalid value"
+ }
+ ]
+ }
end)
end
Pleroma.Config.put([:instance, :account_activation_required], true)
app_token = insert(:oauth_token, user: nil)
- conn = put_req_header(conn, "authorization", "Bearer " <> app_token.token)
+
+ conn =
+ conn
+ |> put_req_header("authorization", "Bearer " <> app_token.token)
+ |> put_req_header("content-type", "application/json")
res =
conn
res =
conn
+ |> put_req_header("content-type", "application/json")
|> Map.put(:remote_ip, {127, 0, 0, 7})
|> post("/api/v1/accounts", Map.delete(valid_params, :email))
res =
conn
+ |> put_req_header("content-type", "application/json")
|> Map.put(:remote_ip, {127, 0, 0, 8})
|> post("/api/v1/accounts", Map.put(valid_params, :email, ""))
end
test "returns forbidden if token is invalid", %{conn: conn, valid_params: valid_params} do
- conn = put_req_header(conn, "authorization", "Bearer " <> "invalid-token")
+ res =
+ conn
+ |> put_req_header("authorization", "Bearer " <> "invalid-token")
+ |> put_req_header("content-type", "multipart/form-data")
+ |> post("/api/v1/accounts", valid_params)
- res = post(conn, "/api/v1/accounts", valid_params)
assert json_response(res, 403) == %{"error" => "Invalid credentials"}
end
response =
build_conn()
|> Plug.Conn.put_req_header("authorization", "Bearer " <> token)
+ |> put_req_header("content-type", "multipart/form-data")
|> post("/api/v1/accounts", %{
nickname: "nickanme",
agreement: true,
conn
|> put_req_header("authorization", "Bearer " <> app_token.token)
|> Map.put(:remote_ip, {15, 15, 15, 15})
+ |> put_req_header("content-type", "multipart/form-data")
for i <- 1..2 do
conn =
- post(conn, "/api/v1/accounts", %{
+ conn
+ |> post("/api/v1/accounts", %{
username: "#{i}lain",
email: "#{i}lain@example.org",
password: "PlzDontHackLain",
|> json_response(200)
assert res == [%{"id" => to_string(list.id), "title" => "Test List"}]
+ assert_schema(res, "ListsResponse", ApiSpec.spec())
end
end
setup do: oauth_access(["read:follows"])
test "returns the relationships for the current user", %{user: user, conn: conn} do
- other_user = insert(:user)
+ %{id: other_user_id} = other_user = insert(:user)
{:ok, _user} = User.follow(user, other_user)
- conn = get(conn, "/api/v1/accounts/relationships", %{"id" => [other_user.id]})
-
- assert [relationship] = json_response(conn, 200)
+ assert [%{"id" => ^other_user_id}] =
+ conn
+ |> get("/api/v1/accounts/relationships?id=#{other_user.id}")
+ |> json_response(200)
- assert to_string(other_user.id) == relationship["id"]
+ assert [%{"id" => ^other_user_id}] =
+ conn
+ |> get("/api/v1/accounts/relationships?id[]=#{other_user.id}")
+ |> json_response(200)
end
test "returns an empty list on a bad request", %{conn: conn} do
other_user_id = to_string(other_user.id)
assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
+ assert_schema(json_response(conn, 200), "AccountsResponse", ApiSpec.spec())
end
test "getting a list of blocks" do
other_user_id = to_string(other_user.id)
assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
+ assert_schema(json_response(conn, 200), "AccountsResponse", ApiSpec.spec())
end
end