Commit 0c56f9de authored by Haelwenn's avatar Haelwenn
Browse files

Merge branch 'tests/openapi-everywhere' into 'develop'

Put OpenAPI ~everywhere in tests

See merge request !3324
parents a0731088 f9bedf55
Pipeline #36359 failed with stages
in 11 minutes and 11 seconds
......@@ -25,6 +25,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Don't crash so hard when email settings are invalid.
- Checking activated Upload Filters for required commands.
### Removed
- **Breaking**: Remove deprecated `/api/qvitter/statuses/notifications/read` (replaced by `/api/v1/pleroma/notifications/read`)
## Unreleased (Patch)
### Fixed
......
......@@ -105,6 +105,7 @@ def show_operation do
responses: %{
200 => Operation.response("Media", "application/json", Attachment),
401 => Operation.response("Media", "application/json", ApiError),
403 => Operation.response("Media", "application/json", ApiError),
422 => Operation.response("Media", "application/json", ApiError)
}
}
......
......@@ -115,7 +115,8 @@ def hashtag_operation do
],
operationId: "TimelineController.hashtag",
responses: %{
200 => Operation.response("Array of Status", "application/json", array_of_statuses())
200 => Operation.response("Array of Status", "application/json", array_of_statuses()),
401 => Operation.response("Error", "application/json", ApiError)
}
}
end
......
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do
alias OpenApiSpex.Operation
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Schemas.ApiError
alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
apply(__MODULE__, operation, [])
end
def emoji_operation do
%Operation{
tags: ["Emojis"],
summary: "List all custom emojis",
operationId: "UtilController.emoji",
parameters: [],
responses: %{
200 =>
Operation.response("List", "application/json", %Schema{
type: :object,
additionalProperties: %Schema{
type: :object,
properties: %{
image_url: %Schema{type: :string},
tags: %Schema{type: :array, items: %Schema{type: :string}}
}
},
example: %{
"firefox" => %{
"image_url" => "/emoji/firefox.png",
"tag" => ["Fun"]
}
}
})
}
}
end
def frontend_configurations_operation do
%Operation{
tags: ["Configuration"],
summary: "Dump frontend configurations",
operationId: "UtilController.frontend_configurations",
parameters: [],
responses: %{
200 =>
Operation.response("List", "application/json", %Schema{
type: :object,
additionalProperties: %Schema{type: :object}
})
}
}
end
def change_password_operation do
%Operation{
tags: ["Account credentials"],
summary: "Change account password",
security: [%{"oAuth" => ["write:accounts"]}],
operationId: "UtilController.change_password",
parameters: [
Operation.parameter(:password, :query, :string, "Current password", required: true),
Operation.parameter(:new_password, :query, :string, "New password", required: true),
Operation.parameter(
:new_password_confirmation,
:query,
:string,
"New password, confirmation",
required: true
)
],
responses: %{
200 =>
Operation.response("Success", "application/json", %Schema{
type: :object,
properties: %{status: %Schema{type: :string, example: "success"}}
}),
400 => Operation.response("Error", "application/json", ApiError),
403 => Operation.response("Error", "application/json", ApiError)
}
}
end
def change_email_operation do
%Operation{
tags: ["Account credentials"],
summary: "Change account email",
security: [%{"oAuth" => ["write:accounts"]}],
operationId: "UtilController.change_email",
parameters: [
Operation.parameter(:password, :query, :string, "Current password", required: true),
Operation.parameter(:email, :query, :string, "New email", required: true)
],
requestBody: nil,
responses: %{
200 =>
Operation.response("Success", "application/json", %Schema{
type: :object,
properties: %{status: %Schema{type: :string, example: "success"}}
}),
400 => Operation.response("Error", "application/json", ApiError),
403 => Operation.response("Error", "application/json", ApiError)
}
}
end
def update_notificaton_settings_operation do
%Operation{
tags: ["Accounts"],
summary: "Update Notification Settings",
security: [%{"oAuth" => ["write:accounts"]}],
operationId: "UtilController.update_notificaton_settings",
parameters: [
Operation.parameter(
:block_from_strangers,
:query,
BooleanLike,
"blocks notifications from accounts you do not follow"
),
Operation.parameter(
:hide_notification_contents,
:query,
BooleanLike,
"removes the contents of a message from the push notification"
)
],
requestBody: nil,
responses: %{
200 =>
Operation.response("Success", "application/json", %Schema{
type: :object,
properties: %{status: %Schema{type: :string, example: "success"}}
}),
400 => Operation.response("Error", "application/json", ApiError)
}
}
end
def disable_account_operation do
%Operation{
tags: ["Account credentials"],
summary: "Disable Account",
security: [%{"oAuth" => ["write:accounts"]}],
operationId: "UtilController.disable_account",
parameters: [
Operation.parameter(:password, :query, :string, "Password")
],
responses: %{
200 =>
Operation.response("Success", "application/json", %Schema{
type: :object,
properties: %{status: %Schema{type: :string, example: "success"}}
}),
403 => Operation.response("Error", "application/json", ApiError)
}
}
end
def delete_account_operation do
%Operation{
tags: ["Account credentials"],
summary: "Delete Account",
security: [%{"oAuth" => ["write:accounts"]}],
operationId: "UtilController.delete_account",
parameters: [
Operation.parameter(:password, :query, :string, "Password")
],
responses: %{
200 =>
Operation.response("Success", "application/json", %Schema{
type: :object,
properties: %{status: %Schema{type: :string, example: "success"}}
}),
403 => Operation.response("Error", "application/json", ApiError)
}
}
end
def captcha_operation do
%Operation{
summary: "Get a captcha",
operationId: "UtilController.captcha",
parameters: [],
responses: %{
200 => Operation.response("Success", "application/json", %Schema{type: :object})
}
}
end
def healthcheck_operation do
%Operation{
tags: ["Accounts"],
summary: "Quick status check on the instance",
security: [%{"oAuth" => ["write:accounts"]}],
operationId: "UtilController.healthcheck",
parameters: [],
responses: %{
200 => Operation.response("Healthy", "application/json", %Schema{type: :object}),
503 =>
Operation.response("Disabled or Unhealthy", "application/json", %Schema{type: :object})
}
}
end
def remote_subscribe_operation do
%Operation{
tags: ["Accounts"],
summary: "Remote Subscribe",
operationId: "UtilController.remote_subscribe",
parameters: [],
responses: %{200 => Operation.response("Web Page", "test/html", %Schema{type: :string})}
}
end
end
......@@ -23,6 +23,7 @@ def follow_operation do
requestBody: request_body("Parameters", import_request(), required: true),
responses: %{
200 => ok_response(),
403 => Operation.response("Error", "application/json", ApiError),
500 => Operation.response("Error", "application/json", ApiError)
},
security: [%{"oAuth" => ["write:follow"]}]
......
......@@ -624,12 +624,6 @@ defmodule Pleroma.Web.Router do
get("/oauth_tokens", TwitterAPI.Controller, :oauth_tokens)
delete("/oauth_tokens/:id", TwitterAPI.Controller, :revoke_token)
post(
"/qvitter/statuses/notifications/read",
TwitterAPI.Controller,
:mark_notifications_as_read
)
end
scope "/", Pleroma.Web do
......
......@@ -5,7 +5,6 @@
defmodule Pleroma.Web.TwitterAPI.Controller do
use Pleroma.Web, :controller
alias Pleroma.Notification
alias Pleroma.User
alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
......@@ -14,11 +13,6 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
require Logger
plug(
OAuthScopesPlug,
%{scopes: ["write:notifications"]} when action == :mark_notifications_as_read
)
plug(
:skip_plug,
[OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :confirm_email
......@@ -67,31 +61,4 @@ defp json_reply(conn, status, json) do
|> put_resp_content_type("application/json")
|> send_resp(status, json)
end
def mark_notifications_as_read(
%{assigns: %{user: user}} = conn,
%{"latest_id" => latest_id} = params
) do
Notification.set_read_up_to(user, latest_id)
notifications = Notification.for_user(user, params)
conn
# XXX: This is a hack because pleroma-fe still uses that API.
|> put_view(Pleroma.Web.MastodonAPI.NotificationView)
|> render("index.json", %{notifications: notifications, for: user})
end
def mark_notifications_as_read(%{assigns: %{user: _user}} = conn, _) do
bad_request_reply(conn, "You need to specify latest_id")
end
defp bad_request_reply(conn, error_message) do
json = error_json(conn, error_message)
json_reply(conn, 400, json)
end
defp error_json(conn, error_message) do
%{"error" => error_message, "request" => conn.request_path} |> Jason.encode!()
end
end
......@@ -10,12 +10,12 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
alias Pleroma.Config
alias Pleroma.Emoji
alias Pleroma.Healthcheck
alias Pleroma.Notification
alias Pleroma.User
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.Plugs.OAuthScopesPlug
alias Pleroma.Web.WebFinger
plug(Pleroma.Web.ApiSpec.CastAndValidate when action != :remote_subscribe)
plug(Pleroma.Web.Plugs.FederatingPlug when action == :remote_subscribe)
plug(
......@@ -30,7 +30,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
]
)
plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action == :notifications_read)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.TwitterUtilOperation
def remote_subscribe(conn, %{"nickname" => nick, "profile" => _}) do
with %User{} = user <- User.get_cached_by_nickname(nick),
......@@ -62,17 +62,6 @@ def remote_subscribe(conn, %{"user" => %{"nickname" => nick, "profile" => profil
end
end
def notifications_read(%{assigns: %{user: user}} = conn, %{"id" => notification_id}) do
with {:ok, _} <- Notification.read_one(user, notification_id) do
json(conn, %{status: "success"})
else
{:error, message} ->
conn
|> put_resp_content_type("application/json")
|> send_resp(403, Jason.encode!(%{"error" => message}))
end
end
def frontend_configurations(conn, _params) do
render(conn, "frontend_configurations.json")
end
......@@ -92,13 +81,17 @@ def update_notificaton_settings(%{assigns: %{user: user}} = conn, params) do
end
end
def change_password(%{assigns: %{user: user}} = conn, params) do
case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
def change_password(%{assigns: %{user: user}} = conn, %{
password: password,
new_password: new_password,
new_password_confirmation: new_password_confirmation
}) do
case CommonAPI.Utils.confirm_current_password(user, password) do
{:ok, user} ->
with {:ok, _user} <-
User.reset_password(user, %{
password: params["new_password"],
password_confirmation: params["new_password_confirmation"]
password: new_password,
password_confirmation: new_password_confirmation
}) do
json(conn, %{status: "success"})
else
......@@ -115,10 +108,10 @@ def change_password(%{assigns: %{user: user}} = conn, params) do
end
end
def change_email(%{assigns: %{user: user}} = conn, params) do
case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
def change_email(%{assigns: %{user: user}} = conn, %{password: password, email: email}) do
case CommonAPI.Utils.confirm_current_password(user, password) do
{:ok, user} ->
with {:ok, _user} <- User.change_email(user, params["email"]) do
with {:ok, _user} <- User.change_email(user, email) do
json(conn, %{status: "success"})
else
{:error, changeset} ->
......@@ -135,7 +128,7 @@ def change_email(%{assigns: %{user: user}} = conn, params) do
end
def delete_account(%{assigns: %{user: user}} = conn, params) do
password = params["password"] || ""
password = params[:password] || ""
case CommonAPI.Utils.confirm_current_password(user, password) do
{:ok, user} ->
......@@ -148,7 +141,7 @@ def delete_account(%{assigns: %{user: user}} = conn, params) do
end
def disable_account(%{assigns: %{user: user}} = conn, params) do
case CommonAPI.Utils.confirm_current_password(user, params["password"]) do
case CommonAPI.Utils.confirm_current_password(user, params[:password]) do
{:ok, user} ->
User.set_activation_async(user, false)
json(conn, %{status: "success"})
......
......@@ -514,11 +514,11 @@ test "paginates a user's statuses", %{user: user, conn: conn} do
{:ok, post_2} = CommonAPI.post(user, %{status: "second post"})
response_1 = get(conn, "/api/v1/accounts/#{user.id}/statuses?limit=1")
assert [res] = json_response(response_1, 200)
assert [res] = json_response_and_validate_schema(response_1, 200)
assert res["id"] == post_2.id
response_2 = get(conn, "/api/v1/accounts/#{user.id}/statuses?limit=1&max_id=#{res["id"]}")
assert [res] = json_response(response_2, 200)
assert [res] = json_response_and_validate_schema(response_2, 200)
assert res["id"] == post_1.id
refute response_1 == response_2
......@@ -881,7 +881,7 @@ test "following without reblogs" do
assert [] ==
conn
|> get("/api/v1/timelines/home")
|> json_response(200)
|> json_response_and_validate_schema(200)
assert %{"showing_reblogs" => true} =
conn
......@@ -892,7 +892,7 @@ test "following without reblogs" do
assert [%{"id" => ^reblog_id}] =
conn
|> get("/api/v1/timelines/home")
|> json_response(200)
|> json_response_and_validate_schema(200)
end
test "following with reblogs" do
......@@ -910,7 +910,7 @@ test "following with reblogs" do
assert [%{"id" => ^reblog_id}] =
conn
|> get("/api/v1/timelines/home")
|> json_response(200)
|> json_response_and_validate_schema(200)
assert %{"showing_reblogs" => false} =
conn
......@@ -921,7 +921,7 @@ test "following with reblogs" do
assert [] ==
conn
|> get("/api/v1/timelines/home")
|> json_response(200)
|> json_response_and_validate_schema(200)
end
test "following / unfollowing errors", %{user: user, conn: conn} do
......
......@@ -214,7 +214,8 @@ test "(vanilla) Mastodon frontend behaviour", %{user: user_one, conn: conn} do
res_conn = get(conn, "/api/v1/statuses/#{direct.id}/context")
assert %{"ancestors" => [], "descendants" => []} == json_response(res_conn, 200)
assert %{"ancestors" => [], "descendants" => []} ==
json_response_and_validate_schema(res_conn, 200)
end
test "Removes a conversation", %{user: user_one, conn: conn} do
......
......@@ -140,7 +140,7 @@ test "it returns 403 if media object requested by non-owner", %{object: object,
conn
|> get("/api/v1/media/#{object.id}")
|> json_response(403)
|> json_response_and_validate_schema(403)
end
end
end
......@@ -82,6 +82,7 @@ test "posting a status", %{conn: conn} do
"sensitive" => 0
})
# Idempotency plug response means detection fail
assert %{"id" => second_id} = json_response(conn_two, 200)
assert id == second_id
......@@ -1559,7 +1560,7 @@ test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{c
|> assign(:token, insert(:oauth_token, user: user3, scopes: ["read:statuses"]))
|> get("api/v1/timelines/home")
[reblogged_activity] = json_response(conn3, 200)
[reblogged_activity] = json_response_and_validate_schema(conn3, 200)
assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id
......@@ -1913,7 +1914,7 @@ test "posting a local only status" do
local = Utils.as_local_public()
assert %{"content" => "cofe", "id" => id, "visibility" => "local"} =
json_response(conn_one, 200)
json_response_and_validate_schema(conn_one, 200)
assert %Activity{id: ^id, data: %{"to" => [^local]}} = Activity.get_by_id(id)
end
......
......@@ -905,10 +905,10 @@ defp ensure_authenticated_access(base_uri) do
%{conn: auth_conn} = oauth_access(["read:statuses"])
res_conn = get(auth_conn, "#{base_uri}?local=true")
assert length(json_response(res_conn, 200)) == 1
assert length(json_response_and_validate_schema(res_conn, 200)) == 1
res_conn = get(auth_conn, "#{base_uri}?local=false")
assert length(json_response(res_conn, 200)) == 2
assert length(json_response_and_validate_schema(res_conn, 200)) == 2
end
test "with default settings on private instances, returns 403 for unauthenticated users", %{
......@@ -922,7 +922,7 @@ test "with default settings on private instances, returns 403 for unauthenticate
for local <- [true, false] do
res_conn = get(conn, "#{base_uri}?local=#{local}")
assert json_response(res_conn, :unauthorized) == error_response
assert json_response_and_validate_schema(res_conn, :unauthorized) == error_response
end
ensure_authenticated_access(base_uri)
......@@ -939,7 +939,7 @@ test "with `%{local: true, federated: true}`, returns 403 for unauthenticated us
for local <- [true, false] do
res_conn = get(conn, "#{base_uri}?local=#{local}")
assert json_response(res_conn, :unauthorized) == error_response
assert json_response_and_validate_schema(res_conn, :unauthorized) == error_response
end
ensure_authenticated_access(base_uri)
......@@ -951,10 +951,10 @@ test "with `%{local: false, federated: true}`, forbids unauthenticated access to
clear_config([:restrict_unauthenticated, :timelines, :federated], true)
res_conn = get(conn, "#{base_uri}?local=true")
assert length(json_response(res_conn, 200)) == 1
assert length(json_response_and_validate_schema(res_conn, 200)) == 1
res_conn = get(conn, "#{base_uri}?local=false")
assert json_response(res_conn, :unauthorized) == error_response
assert json_response_and_validate_schema(res_conn, :unauthorized) == error_response
ensure_authenticated_access(base_uri)
end
......@@ -966,11 +966,11 @@ test "with `%{local: true, federated: false}`, forbids unauthenticated access to
clear_config([:restrict_unauthenticated, :timelines, :federated], false)
res_conn = get(conn, "#{base_uri}?local=true")
assert json_response(res_conn, :unauthorized) == error_response
assert json_response_and_validate_schema(res_conn, :unauthorized) == error_response
# Note: local activities get delivered as part of federated timeline
res_conn = get(conn, "#{base_uri}?local=false")
assert length(json_response(res_conn, 200)) == 2
assert length(json_response_and_validate_schema(res_conn, 200)) == 2
ensure_authenticated_access(base_uri)
end
......
......@@ -20,7 +20,7 @@ test "put settings", %{conn: conn} do
|> assign(:token, insert(:oauth_token, user: user, scopes: ["write:accounts"]))
|> put("/api/web/settings", %{"data" => %{"programming" => "socks"}})
assert _result = json_response(conn, 200)
assert %{} = json_response(conn, 200)
user = User.get_cached_by_ap_id(user.ap_id)