Use json_response_and_validate_schema/2 in tests to validate OpenAPI schema

This commit is contained in:
Egor Kislitsyn 2020-04-27 20:46:52 +04:00
parent f1ca917bb0
commit 2efc00b3cf
No known key found for this signature in database
GPG key ID: 1B49CB15B71E7805
7 changed files with 330 additions and 358 deletions

View file

@ -7,6 +7,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
alias OpenApiSpex.Reference alias OpenApiSpex.Reference
alias OpenApiSpex.Schema alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Schemas.Account alias Pleroma.Web.ApiSpec.Schemas.Account
alias Pleroma.Web.ApiSpec.Schemas.ApiError
alias Pleroma.Web.ApiSpec.Schemas.AccountCreateRequest alias Pleroma.Web.ApiSpec.Schemas.AccountCreateRequest
alias Pleroma.Web.ApiSpec.Schemas.AccountCreateResponse alias Pleroma.Web.ApiSpec.Schemas.AccountCreateResponse
alias Pleroma.Web.ApiSpec.Schemas.AccountFollowsRequest alias Pleroma.Web.ApiSpec.Schemas.AccountFollowsRequest
@ -38,7 +39,10 @@ def create_operation do
operationId: "AccountController.create", operationId: "AccountController.create",
requestBody: request_body("Parameters", AccountCreateRequest, required: true), requestBody: request_body("Parameters", AccountCreateRequest, required: true),
responses: %{ responses: %{
200 => Operation.response("Account", "application/json", AccountCreateResponse) 200 => Operation.response("Account", "application/json", AccountCreateResponse),
400 => Operation.response("Error", "application/json", ApiError),
403 => Operation.response("Error", "application/json", ApiError),
429 => Operation.response("Error", "application/json", ApiError)
} }
} }
end end
@ -65,7 +69,8 @@ def update_credentials_operation do
security: [%{"oAuth" => ["write:accounts"]}], security: [%{"oAuth" => ["write:accounts"]}],
requestBody: request_body("Parameters", AccountUpdateCredentialsRequest, required: true), requestBody: request_body("Parameters", AccountUpdateCredentialsRequest, required: true),
responses: %{ responses: %{
200 => Operation.response("Account", "application/json", Account) 200 => Operation.response("Account", "application/json", Account),
403 => Operation.response("Error", "application/json", ApiError)
} }
} }
end end
@ -102,7 +107,8 @@ def show_operation do
description: "View information about a profile.", description: "View information about a profile.",
parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}], parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
responses: %{ responses: %{
200 => Operation.response("Account", "application/json", Account) 200 => Operation.response("Account", "application/json", Account),
404 => Operation.response("Error", "application/json", ApiError)
} }
} }
end end
@ -140,7 +146,8 @@ def statuses_operation do
) )
] ++ pagination_params(), ] ++ pagination_params(),
responses: %{ responses: %{
200 => Operation.response("Statuses", "application/json", StatusesResponse) 200 => Operation.response("Statuses", "application/json", StatusesResponse),
404 => Operation.response("Error", "application/json", ApiError)
} }
} }
end end
@ -204,7 +211,9 @@ def follow_operation do
) )
], ],
responses: %{ responses: %{
200 => Operation.response("Relationship", "application/json", AccountRelationship) 200 => Operation.response("Relationship", "application/json", AccountRelationship),
400 => Operation.response("Error", "application/json", ApiError),
404 => Operation.response("Error", "application/json", ApiError)
} }
} }
end end
@ -218,7 +227,9 @@ def unfollow_operation do
description: "Unfollow the given account", description: "Unfollow the given account",
parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}], parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
responses: %{ responses: %{
200 => Operation.response("Relationship", "application/json", AccountRelationship) 200 => Operation.response("Relationship", "application/json", AccountRelationship),
400 => Operation.response("Error", "application/json", ApiError),
404 => Operation.response("Error", "application/json", ApiError)
} }
} }
end end
@ -298,7 +309,9 @@ def follows_operation do
security: [%{"oAuth" => ["follow", "write:follows"]}], security: [%{"oAuth" => ["follow", "write:follows"]}],
requestBody: request_body("Parameters", AccountFollowsRequest, required: true), requestBody: request_body("Parameters", AccountFollowsRequest, required: true),
responses: %{ responses: %{
200 => Operation.response("Account", "application/json", AccountRelationship) 200 => Operation.response("Account", "application/json", AccountRelationship),
400 => Operation.response("Error", "application/json", ApiError),
404 => Operation.response("Error", "application/json", ApiError)
} }
} }
end end

View file

@ -41,7 +41,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
type: :object, type: :object,
properties: %{ properties: %{
allow_following_move: %Schema{type: :boolean}, allow_following_move: %Schema{type: :boolean},
background_image: %Schema{type: :boolean, nullable: true}, background_image: %Schema{type: :string, nullable: true},
chat_token: %Schema{type: :string}, chat_token: %Schema{type: :string},
confirmation_pending: %Schema{type: :boolean}, confirmation_pending: %Schema{type: :boolean},
hide_favorites: %Schema{type: :boolean}, hide_favorites: %Schema{type: :boolean},

View file

@ -82,8 +82,9 @@ def add_link_headers(conn, activities, extra_params) do
end end
end end
def assign_account_by_id(%{params: %{"id" => id}} = conn, _) do def assign_account_by_id(conn, _) do
case Pleroma.User.get_cached_by_id(id) do # TODO: use `conn.params[:id]` only after moving to OpenAPI
case Pleroma.User.get_cached_by_id(conn.params[:id] || conn.params["id"]) do
%Pleroma.User{} = account -> assign(conn, :account, account) %Pleroma.User{} = account -> assign(conn, :account, account)
nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt() nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt()
end end

View file

@ -26,6 +26,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
alias Pleroma.Web.OAuth.Token alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.TwitterAPI.TwitterAPI alias Pleroma.Web.TwitterAPI.TwitterAPI
plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError)
plug(:skip_plug, OAuthScopesPlug when action == :identity_proofs) plug(:skip_plug, OAuthScopesPlug when action == :identity_proofs)
plug( plug(
@ -83,8 +85,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
plug(RateLimiter, [name: :app_account_creation] when action == :create) plug(RateLimiter, [name: :app_account_creation] when action == :create)
plug(:assign_account_by_id when action in @needs_account) 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) action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.AccountOperation defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.AccountOperation

View file

@ -56,7 +56,14 @@ defp request_content_type(%{conn: conn}) do
[conn: conn] [conn: conn]
end end
defp json_response_and_validate_schema(conn, status \\ nil) do defp json_response_and_validate_schema(
%{
private: %{
open_api_spex: %{operation_id: op_id, operation_lookup: lookup, spec: spec}
}
} = conn,
status
) do
content_type = content_type =
conn conn
|> Plug.Conn.get_resp_header("content-type") |> Plug.Conn.get_resp_header("content-type")
@ -64,10 +71,12 @@ defp json_response_and_validate_schema(conn, status \\ nil) do
|> String.split(";") |> String.split(";")
|> List.first() |> List.first()
status = status || conn.status status = Plug.Conn.Status.code(status)
%{private: %{open_api_spex: %{operation_id: op_id, operation_lookup: lookup, spec: spec}}} = unless lookup[op_id].responses[status] do
conn err = "Response schema not found for #{conn.status} #{conn.method} #{conn.request_path}"
flunk(err)
end
schema = lookup[op_id].responses[status].content[content_type].schema schema = lookup[op_id].responses[status].content[content_type].schema
json = json_response(conn, status) json = json_response(conn, status)
@ -92,6 +101,10 @@ defp json_response_and_validate_schema(conn, status \\ nil) do
end end
end end
defp json_response_and_validate_schema(conn, _status) do
flunk("Response schema not found for #{conn.method} #{conn.request_path} #{conn.status}")
end
defp ensure_federating_or_authenticated(conn, url, user) do defp ensure_federating_or_authenticated(conn, url, user) do
initial_setting = Config.get([:instance, :federating]) initial_setting = Config.get([:instance, :federating])
on_exit(fn -> Config.put([:instance, :federating], initial_setting) end) on_exit(fn -> Config.put([:instance, :federating], initial_setting) end)

View file

@ -26,7 +26,7 @@ test "sets user settings in a generic way", %{conn: conn} do
} }
}) })
assert user_data = json_response(res_conn, 200) assert user_data = json_response_and_validate_schema(res_conn, 200)
assert user_data["pleroma"]["settings_store"] == %{"pleroma_fe" => %{"theme" => "bla"}} assert user_data["pleroma"]["settings_store"] == %{"pleroma_fe" => %{"theme" => "bla"}}
user = Repo.get(User, user_data["id"]) user = Repo.get(User, user_data["id"])
@ -42,7 +42,7 @@ test "sets user settings in a generic way", %{conn: conn} do
} }
}) })
assert user_data = json_response(res_conn, 200) assert user_data = json_response_and_validate_schema(res_conn, 200)
assert user_data["pleroma"]["settings_store"] == assert user_data["pleroma"]["settings_store"] ==
%{ %{
@ -63,7 +63,7 @@ test "sets user settings in a generic way", %{conn: conn} do
} }
}) })
assert user_data = json_response(res_conn, 200) assert user_data = json_response_and_validate_schema(res_conn, 200)
assert user_data["pleroma"]["settings_store"] == assert user_data["pleroma"]["settings_store"] ==
%{ %{
@ -80,7 +80,7 @@ test "updates the user's bio", %{conn: conn} do
"note" => "I drink #cofe with @#{user2.nickname}\n\nsuya.." "note" => "I drink #cofe with @#{user2.nickname}\n\nsuya.."
}) })
assert user_data = json_response(conn, 200) assert user_data = json_response_and_validate_schema(conn, 200)
assert user_data["note"] == assert user_data["note"] ==
~s(I drink <a class="hashtag" data-tag="cofe" href="http://localhost:4001/tag/cofe">#cofe</a> with <span class="h-card"><a class="u-url mention" data-user="#{ ~s(I drink <a class="hashtag" data-tag="cofe" href="http://localhost:4001/tag/cofe">#cofe</a> with <span class="h-card"><a class="u-url mention" data-user="#{
@ -91,7 +91,7 @@ test "updates the user's bio", %{conn: conn} do
test "updates the user's locking status", %{conn: conn} do test "updates the user's locking status", %{conn: conn} do
conn = patch(conn, "/api/v1/accounts/update_credentials", %{locked: "true"}) conn = patch(conn, "/api/v1/accounts/update_credentials", %{locked: "true"})
assert user_data = json_response(conn, 200) assert user_data = json_response_and_validate_schema(conn, 200)
assert user_data["locked"] == true assert user_data["locked"] == true
end end
@ -101,21 +101,21 @@ test "updates the user's allow_following_move", %{user: user, conn: conn} do
conn = patch(conn, "/api/v1/accounts/update_credentials", %{allow_following_move: "false"}) conn = patch(conn, "/api/v1/accounts/update_credentials", %{allow_following_move: "false"})
assert refresh_record(user).allow_following_move == false assert refresh_record(user).allow_following_move == false
assert user_data = json_response(conn, 200) assert user_data = json_response_and_validate_schema(conn, 200)
assert user_data["pleroma"]["allow_following_move"] == false assert user_data["pleroma"]["allow_following_move"] == false
end end
test "updates the user's default scope", %{conn: conn} do test "updates the user's default scope", %{conn: conn} do
conn = patch(conn, "/api/v1/accounts/update_credentials", %{default_scope: "unlisted"}) conn = patch(conn, "/api/v1/accounts/update_credentials", %{default_scope: "unlisted"})
assert user_data = json_response(conn, 200) assert user_data = json_response_and_validate_schema(conn, 200)
assert user_data["source"]["privacy"] == "unlisted" assert user_data["source"]["privacy"] == "unlisted"
end end
test "updates the user's hide_followers status", %{conn: conn} do test "updates the user's hide_followers status", %{conn: conn} do
conn = patch(conn, "/api/v1/accounts/update_credentials", %{hide_followers: "true"}) conn = patch(conn, "/api/v1/accounts/update_credentials", %{hide_followers: "true"})
assert user_data = json_response(conn, 200) assert user_data = json_response_and_validate_schema(conn, 200)
assert user_data["pleroma"]["hide_followers"] == true assert user_data["pleroma"]["hide_followers"] == true
end end
@ -123,12 +123,12 @@ test "updates the user's discoverable status", %{conn: conn} do
assert %{"source" => %{"pleroma" => %{"discoverable" => true}}} = assert %{"source" => %{"pleroma" => %{"discoverable" => true}}} =
conn conn
|> patch("/api/v1/accounts/update_credentials", %{discoverable: "true"}) |> patch("/api/v1/accounts/update_credentials", %{discoverable: "true"})
|> json_response(:ok) |> json_response_and_validate_schema(:ok)
assert %{"source" => %{"pleroma" => %{"discoverable" => false}}} = assert %{"source" => %{"pleroma" => %{"discoverable" => false}}} =
conn conn
|> patch("/api/v1/accounts/update_credentials", %{discoverable: "false"}) |> patch("/api/v1/accounts/update_credentials", %{discoverable: "false"})
|> json_response(:ok) |> json_response_and_validate_schema(:ok)
end end
test "updates the user's hide_followers_count and hide_follows_count", %{conn: conn} do test "updates the user's hide_followers_count and hide_follows_count", %{conn: conn} do
@ -138,7 +138,7 @@ test "updates the user's hide_followers_count and hide_follows_count", %{conn: c
hide_follows_count: "true" hide_follows_count: "true"
}) })
assert user_data = json_response(conn, 200) assert user_data = json_response_and_validate_schema(conn, 200)
assert user_data["pleroma"]["hide_followers_count"] == true assert user_data["pleroma"]["hide_followers_count"] == true
assert user_data["pleroma"]["hide_follows_count"] == true assert user_data["pleroma"]["hide_follows_count"] == true
end end
@ -147,7 +147,7 @@ test "updates the user's skip_thread_containment option", %{user: user, conn: co
response = response =
conn conn
|> patch("/api/v1/accounts/update_credentials", %{skip_thread_containment: "true"}) |> patch("/api/v1/accounts/update_credentials", %{skip_thread_containment: "true"})
|> json_response(200) |> json_response_and_validate_schema(200)
assert response["pleroma"]["skip_thread_containment"] == true assert response["pleroma"]["skip_thread_containment"] == true
assert refresh_record(user).skip_thread_containment assert refresh_record(user).skip_thread_containment
@ -156,28 +156,28 @@ test "updates the user's skip_thread_containment option", %{user: user, conn: co
test "updates the user's hide_follows status", %{conn: conn} do test "updates the user's hide_follows status", %{conn: conn} do
conn = patch(conn, "/api/v1/accounts/update_credentials", %{hide_follows: "true"}) conn = patch(conn, "/api/v1/accounts/update_credentials", %{hide_follows: "true"})
assert user_data = json_response(conn, 200) assert user_data = json_response_and_validate_schema(conn, 200)
assert user_data["pleroma"]["hide_follows"] == true assert user_data["pleroma"]["hide_follows"] == true
end end
test "updates the user's hide_favorites status", %{conn: conn} do test "updates the user's hide_favorites status", %{conn: conn} do
conn = patch(conn, "/api/v1/accounts/update_credentials", %{hide_favorites: "true"}) conn = patch(conn, "/api/v1/accounts/update_credentials", %{hide_favorites: "true"})
assert user_data = json_response(conn, 200) assert user_data = json_response_and_validate_schema(conn, 200)
assert user_data["pleroma"]["hide_favorites"] == true assert user_data["pleroma"]["hide_favorites"] == true
end end
test "updates the user's show_role status", %{conn: conn} do test "updates the user's show_role status", %{conn: conn} do
conn = patch(conn, "/api/v1/accounts/update_credentials", %{show_role: "false"}) conn = patch(conn, "/api/v1/accounts/update_credentials", %{show_role: "false"})
assert user_data = json_response(conn, 200) assert user_data = json_response_and_validate_schema(conn, 200)
assert user_data["source"]["pleroma"]["show_role"] == false assert user_data["source"]["pleroma"]["show_role"] == false
end end
test "updates the user's no_rich_text status", %{conn: conn} do test "updates the user's no_rich_text status", %{conn: conn} do
conn = patch(conn, "/api/v1/accounts/update_credentials", %{no_rich_text: "true"}) conn = patch(conn, "/api/v1/accounts/update_credentials", %{no_rich_text: "true"})
assert user_data = json_response(conn, 200) assert user_data = json_response_and_validate_schema(conn, 200)
assert user_data["source"]["pleroma"]["no_rich_text"] == true assert user_data["source"]["pleroma"]["no_rich_text"] == true
end end
@ -185,7 +185,7 @@ test "updates the user's name", %{conn: conn} do
conn = conn =
patch(conn, "/api/v1/accounts/update_credentials", %{"display_name" => "markorepairs"}) patch(conn, "/api/v1/accounts/update_credentials", %{"display_name" => "markorepairs"})
assert user_data = json_response(conn, 200) assert user_data = json_response_and_validate_schema(conn, 200)
assert user_data["display_name"] == "markorepairs" assert user_data["display_name"] == "markorepairs"
end end
@ -198,7 +198,7 @@ test "updates the user's avatar", %{user: user, conn: conn} do
conn = patch(conn, "/api/v1/accounts/update_credentials", %{"avatar" => new_avatar}) conn = patch(conn, "/api/v1/accounts/update_credentials", %{"avatar" => new_avatar})
assert user_response = json_response(conn, 200) assert user_response = json_response_and_validate_schema(conn, 200)
assert user_response["avatar"] != User.avatar_url(user) assert user_response["avatar"] != User.avatar_url(user)
end end
@ -211,7 +211,7 @@ test "updates the user's banner", %{user: user, conn: conn} do
conn = patch(conn, "/api/v1/accounts/update_credentials", %{"header" => new_header}) conn = patch(conn, "/api/v1/accounts/update_credentials", %{"header" => new_header})
assert user_response = json_response(conn, 200) assert user_response = json_response_and_validate_schema(conn, 200)
assert user_response["header"] != User.banner_url(user) assert user_response["header"] != User.banner_url(user)
end end
@ -227,7 +227,7 @@ test "updates the user's background", %{conn: conn} do
"pleroma_background_image" => new_header "pleroma_background_image" => new_header
}) })
assert user_response = json_response(conn, 200) assert user_response = json_response_and_validate_schema(conn, 200)
assert user_response["pleroma"]["background_image"] assert user_response["pleroma"]["background_image"]
end end
@ -244,9 +244,9 @@ test "requires 'write:accounts' permission" do
if token == token1 do if token == token1 do
assert %{"error" => "Insufficient permissions: write:accounts."} == assert %{"error" => "Insufficient permissions: write:accounts."} ==
json_response(conn, 403) json_response_and_validate_schema(conn, 403)
else else
assert json_response(conn, 200) assert json_response_and_validate_schema(conn, 200)
end end
end end
end end
@ -261,11 +261,11 @@ test "updates profile emojos", %{user: user, conn: conn} do
"display_name" => name "display_name" => name
}) })
assert json_response(ret_conn, 200) assert json_response_and_validate_schema(ret_conn, 200)
conn = get(conn, "/api/v1/accounts/#{user.id}") conn = get(conn, "/api/v1/accounts/#{user.id}")
assert user_data = json_response(conn, 200) assert user_data = json_response_and_validate_schema(conn, 200)
assert user_data["note"] == note assert user_data["note"] == note
assert user_data["display_name"] == name assert user_data["display_name"] == name
@ -281,7 +281,7 @@ test "update fields", %{conn: conn} do
account_data = account_data =
conn conn
|> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields}) |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
|> json_response(200) |> json_response_and_validate_schema(200)
assert account_data["fields"] == [ assert account_data["fields"] == [
%{"name" => "<a href=\"http://google.com\">foo</a>", "value" => "bar"}, %{"name" => "<a href=\"http://google.com\">foo</a>", "value" => "bar"},
@ -314,7 +314,7 @@ test "update fields via x-www-form-urlencoded", %{conn: conn} do
conn conn
|> put_req_header("content-type", "application/x-www-form-urlencoded") |> put_req_header("content-type", "application/x-www-form-urlencoded")
|> patch("/api/v1/accounts/update_credentials", fields) |> patch("/api/v1/accounts/update_credentials", fields)
|> json_response(200) |> json_response_and_validate_schema(200)
assert account["fields"] == [ assert account["fields"] == [
%{"name" => "foo", "value" => "bar"}, %{"name" => "foo", "value" => "bar"},
@ -339,7 +339,7 @@ test "update fields with empty name", %{conn: conn} do
account = account =
conn conn
|> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields}) |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
|> json_response(200) |> json_response_and_validate_schema(200)
assert account["fields"] == [ assert account["fields"] == [
%{"name" => "foo", "value" => ""} %{"name" => "foo", "value" => ""}
@ -358,14 +358,14 @@ test "update fields when invalid request", %{conn: conn} do
assert %{"error" => "Invalid request"} == assert %{"error" => "Invalid request"} ==
conn conn
|> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields}) |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
|> json_response(403) |> json_response_and_validate_schema(403)
fields = [%{"name" => long_name, "value" => "bar"}] fields = [%{"name" => long_name, "value" => "bar"}]
assert %{"error" => "Invalid request"} == assert %{"error" => "Invalid request"} ==
conn conn
|> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields}) |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
|> json_response(403) |> json_response_and_validate_schema(403)
Pleroma.Config.put([:instance, :max_account_fields], 1) Pleroma.Config.put([:instance, :max_account_fields], 1)
@ -377,7 +377,7 @@ test "update fields when invalid request", %{conn: conn} do
assert %{"error" => "Invalid request"} == assert %{"error" => "Invalid request"} ==
conn conn
|> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields}) |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
|> json_response(403) |> json_response_and_validate_schema(403)
end end
end end
end end

File diff suppressed because it is too large Load diff