Merge branch 'feature/1893-remote-emoji-packs-pagination' into 'develop'

Feature/1893 remote emoji packs pagination

Closes #1893

See merge request pleroma/pleroma!2698
This commit is contained in:
feld 2020-10-05 17:52:02 +00:00
commit 4d852f3e78
11 changed files with 244 additions and 139 deletions

View File

@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Changed ### Changed
- **Breaking:** Pleroma Admin API: emoji packs and files routes changed.
- Search: Users are now findable by their urls. - Search: Users are now findable by their urls.
- Renamed `:await_up_timeout` in `:connections_pool` namespace to `:connect_timeout`, old name is deprecated. - Renamed `:await_up_timeout` in `:connections_pool` namespace to `:connect_timeout`, old name is deprecated.
- Renamed `:timeout` in `pools` namespace to `:recv_timeout`, old name is deprecated. - Renamed `:timeout` in `pools` namespace to `:recv_timeout`, old name is deprecated.
@ -24,7 +25,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Media preview proxy (requires `ffmpeg` and `ImageMagick` to be installed and media proxy to be enabled; see `:media_preview_proxy` config for more details). - Media preview proxy (requires `ffmpeg` and `ImageMagick` to be installed and media proxy to be enabled; see `:media_preview_proxy` config for more details).
- Pleroma API: Importing the mutes users from CSV files. - Pleroma API: Importing the mutes users from CSV files.
- Experimental websocket-based federation between Pleroma instances. - Experimental websocket-based federation between Pleroma instances.
<details>
<summary>API Changes</summary>
- Pleroma API: Importing the mutes users from CSV files.
- Admin API: Importing emoji from a zip file - Admin API: Importing emoji from a zip file
- Pleroma API: Pagination for remote/local packs and emoji.
</details>
### Removed ### Removed

View File

@ -378,44 +378,43 @@ The status posting endpoint takes an additional parameter, `in_reply_to_conversa
* Params: None * Params: None
* Response: JSON, returns a list of Mastodon Conversation entities that were marked as read (200 - healthy, 503 unhealthy). * Response: JSON, returns a list of Mastodon Conversation entities that were marked as read (200 - healthy, 503 unhealthy).
## `GET /api/pleroma/emoji/packs/import` ## `GET /api/pleroma/emoji/pack?name=:name`
### Imports packs from filesystem
### Get pack.json for the pack
* Method `GET` * Method `GET`
* Authentication: required * Authentication: not required
* Params: None
* Response: JSON, returns a list of imported packs.
## `GET /api/pleroma/emoji/packs/remote`
### Make request to another instance for packs list
* Method `GET`
* Authentication: required
* Params: * Params:
* `url`: url of the instance to get packs from * `page`: page number for files (default 1)
* Response: JSON with the pack list, hashmap with pack name and pack contents * `page_size`: page size for files (default 30)
* Response: JSON, pack json with `files`, `files_count` and `pack` keys with 200 status or 404 if the pack does not exist.
## `POST /api/pleroma/emoji/packs/download` ```json
### Download pack from another instance {
* Method `POST` "files": {...},
* Authentication: required "files_count": 0, // emoji count in pack
* Params: "pack": {...}
* `url`: url of the instance to download from }
* `name`: pack to download from that instance ```
* `as`: (*optional*) name how to save pack
* Response: JSON, "ok" with 200 status if the pack was downloaded, or 500 if there were ## `POST /api/pleroma/emoji/pack?name=:name`
errors downloading the pack
## `POST /api/pleroma/emoji/packs/:name`
### Creates an empty pack ### Creates an empty pack
* Method `POST` * Method `POST`
* Authentication: required * Authentication: required (admin)
* Params: None * Params:
* `name`: pack name
* Response: JSON, "ok" and 200 status or 409 if the pack with that name already exists * Response: JSON, "ok" and 200 status or 409 if the pack with that name already exists
## `PATCH /api/pleroma/emoji/packs/:name` ## `PATCH /api/pleroma/emoji/pack?name=:name`
### Updates (replaces) pack metadata ### Updates (replaces) pack metadata
* Method `PATCH` * Method `PATCH`
* Authentication: required * Authentication: required (admin)
* Params: * Params:
* `name`: pack name
* `metadata`: metadata to replace the old one * `metadata`: metadata to replace the old one
* `license`: Pack license * `license`: Pack license
* `homepage`: Pack home page url * `homepage`: Pack home page url
@ -426,39 +425,85 @@ The status posting endpoint takes an additional parameter, `in_reply_to_conversa
* Response: JSON, updated "metadata" section of the pack and 200 status or 400 if there was a * Response: JSON, updated "metadata" section of the pack and 200 status or 400 if there was a
problem with the new metadata (the error is specified in the "error" part of the response JSON) problem with the new metadata (the error is specified in the "error" part of the response JSON)
## `DELETE /api/pleroma/emoji/packs/:name` ## `DELETE /api/pleroma/emoji/pack?name=:name`
### Delete a custom emoji pack ### Delete a custom emoji pack
* Method `DELETE` * Method `DELETE`
* Authentication: required * Authentication: required (admin)
* Params: None * Params:
* `name`: pack name
* Response: JSON, "ok" and 200 status or 500 if there was an error deleting the pack * Response: JSON, "ok" and 200 status or 500 if there was an error deleting the pack
## `POST /api/pleroma/emoji/packs/:name/files` ## `GET /api/pleroma/emoji/packs/import`
### Add new file to the pack
* Method `POST` ### Imports packs from filesystem
* Authentication: required
* Method `GET`
* Authentication: required (admin)
* Params: None
* Response: JSON, returns a list of imported packs.
## `GET /api/pleroma/emoji/packs/remote`
### Make request to another instance for packs list
* Method `GET`
* Authentication: required (admin)
* Params: * Params:
* `url`: url of the instance to get packs from
* `page`: page number for packs (default 1)
* `page_size`: page size for packs (default 50)
* Response: JSON with the pack list, hashmap with pack name and pack contents
## `POST /api/pleroma/emoji/packs/download`
### Download pack from another instance
* Method `POST`
* Authentication: required (admin)
* Params:
* `url`: url of the instance to download from
* `name`: pack to download from that instance
* `as`: (*optional*) name how to save pack
* Response: JSON, "ok" with 200 status if the pack was downloaded, or 500 if there were
errors downloading the pack
## `POST /api/pleroma/emoji/packs/files?name=:name`
### Add new file to the pack
* Method `POST`
* Authentication: required (admin)
* Params:
* `name`: pack name
* `file`: file needs to be uploaded with the multipart request or link to remote file. * `file`: file needs to be uploaded with the multipart request or link to remote file.
* `shortcode`: (*optional*) shortcode for new emoji, must be unique for all emoji. If not sended, shortcode will be taken from original filename. * `shortcode`: (*optional*) shortcode for new emoji, must be unique for all emoji. If not sended, shortcode will be taken from original filename.
* `filename`: (*optional*) new emoji file name. If not specified will be taken from original filename. * `filename`: (*optional*) new emoji file name. If not specified will be taken from original filename.
* Response: JSON, list of files for updated pack (hashmap -> shortcode => filename) with status 200, either error status with error message. * Response: JSON, list of files for updated pack (hashmap -> shortcode => filename) with status 200, either error status with error message.
## `PATCH /api/pleroma/emoji/packs/:name/files` ## `PATCH /api/pleroma/emoji/packs/files?name=:name`
### Update emoji file from pack ### Update emoji file from pack
* Method `PATCH` * Method `PATCH`
* Authentication: required * Authentication: required (admin)
* Params: * Params:
* `name`: pack name
* `shortcode`: emoji file shortcode * `shortcode`: emoji file shortcode
* `new_shortcode`: new emoji file shortcode * `new_shortcode`: new emoji file shortcode
* `new_filename`: new filename for emoji file * `new_filename`: new filename for emoji file
* `force`: (*optional*) with true value to overwrite existing emoji with new shortcode * `force`: (*optional*) with true value to overwrite existing emoji with new shortcode
* Response: JSON, list with updated files for updated pack (hashmap -> shortcode => filename) with status 200, either error status with error message. * Response: JSON, list with updated files for updated pack (hashmap -> shortcode => filename) with status 200, either error status with error message.
## `DELETE /api/pleroma/emoji/packs/:name/files` ## `DELETE /api/pleroma/emoji/packs/files?name=:name`
### Delete emoji file from pack ### Delete emoji file from pack
* Method `DELETE` * Method `DELETE`
* Authentication: required * Authentication: required (admin)
* Params: * Params:
* `name`: pack name
* `shortcode`: emoji file shortcode * `shortcode`: emoji file shortcode
* Response: JSON, list with updated files for updated pack (hashmap -> shortcode => filename) with status 200, either error status with error message. * Response: JSON, list with updated files for updated pack (hashmap -> shortcode => filename) with status 200, either error status with error message.
@ -483,30 +528,14 @@ The status posting endpoint takes an additional parameter, `in_reply_to_conversa
} }
``` ```
## `GET /api/pleroma/emoji/packs/:name` ## `GET /api/pleroma/emoji/packs/archive?name=:name`
### Get pack.json for the pack ### Requests a local pack archive from the instance
* Method `GET` * Method `GET`
* Authentication: not required * Authentication: not required
* Params: * Params:
* `page`: page number for files (default 1) * `name`: pack name
* `page_size`: page size for files (default 30)
* Response: JSON, pack json with `files`, `files_count` and `pack` keys with 200 status or 404 if the pack does not exist.
```json
{
"files": {...},
"files_count": 0, // emoji count in pack
"pack": {...}
}
```
## `GET /api/pleroma/emoji/packs/:name/archive`
### Requests a local pack archive from the instance
* Method `GET`
* Authentication: not required
* Params: None
* Response: the archive of the pack with a 200 status code, 403 if the pack is not set as shared, * Response: the archive of the pack with a 200 status code, 403 if the pack is not set as shared,
404 if the pack does not exist 404 if the pack does not exist

View File

@ -198,13 +198,13 @@ def import_from_filesystem do
end end
end end
@spec list_remote(String.t()) :: {:ok, map()} | {:error, atom()} @spec list_remote(keyword()) :: {:ok, map()} | {:error, atom()}
def list_remote(url) do def list_remote(opts) do
uri = url |> String.trim() |> URI.parse() uri = opts[:url] |> String.trim() |> URI.parse()
with :ok <- validate_shareable_packs_available(uri) do with :ok <- validate_shareable_packs_available(uri) do
uri uri
|> URI.merge("/api/pleroma/emoji/packs") |> URI.merge("/api/pleroma/emoji/packs?page=#{opts[:page]}&page_size=#{opts[:page_size]}")
|> http_get() |> http_get()
end end
end end
@ -244,7 +244,8 @@ def download(name, url, as) do
uri = url |> String.trim() |> URI.parse() uri = url |> String.trim() |> URI.parse()
with :ok <- validate_shareable_packs_available(uri), with :ok <- validate_shareable_packs_available(uri),
{:ok, remote_pack} <- uri |> URI.merge("/api/pleroma/emoji/packs/#{name}") |> http_get(), {:ok, remote_pack} <-
uri |> URI.merge("/api/pleroma/emoji/pack?name=#{name}") |> http_get(),
{:ok, %{sha: sha, url: url} = pack_info} <- fetch_pack_info(remote_pack, uri, name), {:ok, %{sha: sha, url: url} = pack_info} <- fetch_pack_info(remote_pack, uri, name),
{:ok, archive} <- download_archive(url, sha), {:ok, archive} <- download_archive(url, sha),
pack <- copy_as(remote_pack, as || name), pack <- copy_as(remote_pack, as || name),
@ -523,7 +524,7 @@ defp get_filename(pack, shortcode) do
defp http_get(%URI{} = url), do: url |> to_string() |> http_get() defp http_get(%URI{} = url), do: url |> to_string() |> http_get()
defp http_get(url) do defp http_get(url) do
with {:ok, %{body: body}} <- url |> Pleroma.HTTP.get() do with {:ok, %{body: body}} <- Pleroma.HTTP.get(url, [], pool: :default) do
Jason.decode(body) Jason.decode(body)
end end
end end
@ -572,7 +573,7 @@ defp fetch_pack_info(remote_pack, uri, name) do
{:ok, {:ok,
%{ %{
sha: sha, sha: sha,
url: URI.merge(uri, "/api/pleroma/emoji/packs/#{name}/archive") |> to_string() url: URI.merge(uri, "/api/pleroma/emoji/packs/archive?name=#{name}") |> to_string()
}} }}
%{"fallback-src" => src, "fallback-src-sha256" => sha} when is_binary(src) -> %{"fallback-src" => src, "fallback-src-sha256" => sha} when is_binary(src) ->

View File

@ -126,7 +126,7 @@ def delete_operation do
end end
defp name_param do defp name_param do
Operation.parameter(:name, :path, :string, "Pack Name", example: "cofe", required: true) Operation.parameter(:name, :query, :string, "Pack Name", example: "cofe", required: true)
end end
defp files_object do defp files_object do

View File

@ -19,7 +19,21 @@ def remote_operation do
tags: ["Emoji Packs"], tags: ["Emoji Packs"],
summary: "Make request to another instance for emoji packs list", summary: "Make request to another instance for emoji packs list",
security: [%{"oAuth" => ["write"]}], security: [%{"oAuth" => ["write"]}],
parameters: [url_param()], parameters: [
url_param(),
Operation.parameter(
:page,
:query,
%Schema{type: :integer, default: 1},
"Page"
),
Operation.parameter(
:page_size,
:query,
%Schema{type: :integer, default: 30},
"Number of emoji to return"
)
],
operationId: "PleromaAPI.EmojiPackController.remote", operationId: "PleromaAPI.EmojiPackController.remote",
responses: %{ responses: %{
200 => emoji_packs_response(), 200 => emoji_packs_response(),
@ -192,7 +206,7 @@ def import_from_filesystem_operation do
end end
defp name_param do defp name_param do
Operation.parameter(:name, :path, :string, "Pack Name", example: "cofe", required: true) Operation.parameter(:name, :query, :string, "Pack Name", example: "cofe", required: true)
end end
defp url_param do defp url_param do

View File

@ -23,8 +23,9 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaEmojiPackOperation defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaEmojiPackOperation
def remote(conn, %{url: url}) do def remote(conn, params) do
with {:ok, packs} <- Pack.list_remote(url) do with {:ok, packs} <-
Pack.list_remote(url: params.url, page_size: params.page_size, page: params.page) do
json(conn, packs) json(conn, packs)
else else
{:error, :not_shareable} -> {:error, :not_shareable} ->

View File

@ -226,6 +226,20 @@ defmodule Pleroma.Web.Router do
end end
scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do
scope "/pack" do
pipe_through(:admin_api)
post("/", EmojiPackController, :create)
patch("/", EmojiPackController, :update)
delete("/", EmojiPackController, :delete)
end
scope "/pack" do
pipe_through(:api)
get("/", EmojiPackController, :show)
end
# Modifying packs # Modifying packs
scope "/packs" do scope "/packs" do
pipe_through(:admin_api) pipe_through(:admin_api)
@ -234,21 +248,17 @@ defmodule Pleroma.Web.Router do
get("/remote", EmojiPackController, :remote) get("/remote", EmojiPackController, :remote)
post("/download", EmojiPackController, :download) post("/download", EmojiPackController, :download)
post("/:name", EmojiPackController, :create) post("/files", EmojiFileController, :create)
patch("/:name", EmojiPackController, :update) patch("/files", EmojiFileController, :update)
delete("/:name", EmojiPackController, :delete) delete("/files", EmojiFileController, :delete)
post("/:name/files", EmojiFileController, :create)
patch("/:name/files", EmojiFileController, :update)
delete("/:name/files", EmojiFileController, :delete)
end end
# Pack info / downloading # Pack info / downloading
scope "/packs" do scope "/packs" do
pipe_through(:api) pipe_through(:api)
get("/", EmojiPackController, :index) get("/", EmojiPackController, :index)
get("/:name", EmojiPackController, :show) get("/archive", EmojiPackController, :archive)
get("/:name/archive", EmojiPackController, :archive)
end end
end end

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 B

View File

@ -0,0 +1,11 @@
{
"files": {
"blank": "blank.png"
},
"pack": {
"description": "Test description",
"homepage": "https://pleroma.social",
"license": "Test license",
"share-files": true
}
}

View File

@ -29,7 +29,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiFileControllerTest do
{:ok, %{admin_conn: admin_conn}} {:ok, %{admin_conn: admin_conn}}
end end
describe "POST/PATCH/DELETE /api/pleroma/emoji/packs/:name/files" do describe "POST/PATCH/DELETE /api/pleroma/emoji/packs/files?name=:name" do
setup do setup do
pack_file = "#{@emoji_path}/test_pack/pack.json" pack_file = "#{@emoji_path}/test_pack/pack.json"
original_content = File.read!(pack_file) original_content = File.read!(pack_file)
@ -56,7 +56,7 @@ test "upload zip file with emojies", %{admin_conn: admin_conn} do
resp = resp =
admin_conn admin_conn
|> put_req_header("content-type", "multipart/form-data") |> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/test_pack/files", %{ |> post("/api/pleroma/emoji/packs/files?name=test_pack", %{
file: %Plug.Upload{ file: %Plug.Upload{
content_type: "application/zip", content_type: "application/zip",
filename: "emojis.zip", filename: "emojis.zip",
@ -83,7 +83,7 @@ test "upload zip file with emojies", %{admin_conn: admin_conn} do
test "create shortcode exists", %{admin_conn: admin_conn} do test "create shortcode exists", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data") |> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/test_pack/files", %{ |> post("/api/pleroma/emoji/packs/files?name=test_pack", %{
shortcode: "blank", shortcode: "blank",
filename: "dir/blank.png", filename: "dir/blank.png",
file: %Plug.Upload{ file: %Plug.Upload{
@ -101,7 +101,7 @@ test "don't rewrite old emoji", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data") |> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/test_pack/files", %{ |> post("/api/pleroma/emoji/packs/files?name=test_pack", %{
shortcode: "blank3", shortcode: "blank3",
filename: "dir/blank.png", filename: "dir/blank.png",
file: %Plug.Upload{ file: %Plug.Upload{
@ -119,7 +119,7 @@ test "don't rewrite old emoji", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data") |> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/test_pack/files", %{ |> patch("/api/pleroma/emoji/packs/files?name=test_pack", %{
shortcode: "blank", shortcode: "blank",
new_shortcode: "blank2", new_shortcode: "blank2",
new_filename: "dir_2/blank_3.png" new_filename: "dir_2/blank_3.png"
@ -135,7 +135,7 @@ test "rewrite old emoji with force option", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data") |> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/test_pack/files", %{ |> post("/api/pleroma/emoji/packs/files?name=test_pack", %{
shortcode: "blank3", shortcode: "blank3",
filename: "dir/blank.png", filename: "dir/blank.png",
file: %Plug.Upload{ file: %Plug.Upload{
@ -153,7 +153,7 @@ test "rewrite old emoji with force option", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data") |> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/test_pack/files", %{ |> patch("/api/pleroma/emoji/packs/files?name=test_pack", %{
shortcode: "blank3", shortcode: "blank3",
new_shortcode: "blank4", new_shortcode: "blank4",
new_filename: "dir_2/blank_3.png", new_filename: "dir_2/blank_3.png",
@ -171,7 +171,7 @@ test "rewrite old emoji with force option", %{admin_conn: admin_conn} do
test "with empty filename", %{admin_conn: admin_conn} do test "with empty filename", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data") |> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/test_pack/files", %{ |> post("/api/pleroma/emoji/packs/files?name=test_pack", %{
shortcode: "blank2", shortcode: "blank2",
filename: "", filename: "",
file: %Plug.Upload{ file: %Plug.Upload{
@ -187,7 +187,7 @@ test "with empty filename", %{admin_conn: admin_conn} do
test "add file with not loaded pack", %{admin_conn: admin_conn} do test "add file with not loaded pack", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data") |> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/not_loaded/files", %{ |> post("/api/pleroma/emoji/packs/files?name=not_loaded", %{
shortcode: "blank3", shortcode: "blank3",
filename: "dir/blank.png", filename: "dir/blank.png",
file: %Plug.Upload{ file: %Plug.Upload{
@ -202,7 +202,7 @@ test "add file with not loaded pack", %{admin_conn: admin_conn} do
test "remove file with not loaded pack", %{admin_conn: admin_conn} do test "remove file with not loaded pack", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> delete("/api/pleroma/emoji/packs/not_loaded/files?shortcode=blank3") |> delete("/api/pleroma/emoji/packs/files?name=not_loaded&shortcode=blank3")
|> json_response_and_validate_schema(:not_found) == %{ |> json_response_and_validate_schema(:not_found) == %{
"error" => "pack \"not_loaded\" is not found" "error" => "pack \"not_loaded\" is not found"
} }
@ -210,7 +210,7 @@ test "remove file with not loaded pack", %{admin_conn: admin_conn} do
test "remove file with empty shortcode", %{admin_conn: admin_conn} do test "remove file with empty shortcode", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> delete("/api/pleroma/emoji/packs/not_loaded/files?shortcode=") |> delete("/api/pleroma/emoji/packs/files?name=not_loaded&shortcode=")
|> json_response_and_validate_schema(:not_found) == %{ |> json_response_and_validate_schema(:not_found) == %{
"error" => "pack \"not_loaded\" is not found" "error" => "pack \"not_loaded\" is not found"
} }
@ -219,7 +219,7 @@ test "remove file with empty shortcode", %{admin_conn: admin_conn} do
test "update file with not loaded pack", %{admin_conn: admin_conn} do test "update file with not loaded pack", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data") |> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/not_loaded/files", %{ |> patch("/api/pleroma/emoji/packs/files?name=not_loaded", %{
shortcode: "blank4", shortcode: "blank4",
new_shortcode: "blank3", new_shortcode: "blank3",
new_filename: "dir_2/blank_3.png" new_filename: "dir_2/blank_3.png"
@ -232,7 +232,7 @@ test "update file with not loaded pack", %{admin_conn: admin_conn} do
test "new with shortcode as file with update", %{admin_conn: admin_conn} do test "new with shortcode as file with update", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data") |> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/test_pack/files", %{ |> post("/api/pleroma/emoji/packs/files?name=test_pack", %{
shortcode: "blank4", shortcode: "blank4",
filename: "dir/blank.png", filename: "dir/blank.png",
file: %Plug.Upload{ file: %Plug.Upload{
@ -250,7 +250,7 @@ test "new with shortcode as file with update", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data") |> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/test_pack/files", %{ |> patch("/api/pleroma/emoji/packs/files?name=test_pack", %{
shortcode: "blank4", shortcode: "blank4",
new_shortcode: "blank3", new_shortcode: "blank3",
new_filename: "dir_2/blank_3.png" new_filename: "dir_2/blank_3.png"
@ -265,7 +265,7 @@ test "new with shortcode as file with update", %{admin_conn: admin_conn} do
assert File.exists?("#{@emoji_path}/test_pack/dir_2/blank_3.png") assert File.exists?("#{@emoji_path}/test_pack/dir_2/blank_3.png")
assert admin_conn assert admin_conn
|> delete("/api/pleroma/emoji/packs/test_pack/files?shortcode=blank3") |> delete("/api/pleroma/emoji/packs/files?name=test_pack&shortcode=blank3")
|> json_response_and_validate_schema(200) == %{ |> json_response_and_validate_schema(200) == %{
"blank" => "blank.png", "blank" => "blank.png",
"blank2" => "blank2.png" "blank2" => "blank2.png"
@ -287,7 +287,7 @@ test "new with shortcode from url", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data") |> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/test_pack/files", %{ |> post("/api/pleroma/emoji/packs/files?name=test_pack", %{
shortcode: "blank_url", shortcode: "blank_url",
file: "https://test-blank/blank_url.png" file: "https://test-blank/blank_url.png"
}) })
@ -307,7 +307,7 @@ test "new without shortcode", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data") |> put_req_header("content-type", "multipart/form-data")
|> post("/api/pleroma/emoji/packs/test_pack/files", %{ |> post("/api/pleroma/emoji/packs/files?name=test_pack", %{
file: %Plug.Upload{ file: %Plug.Upload{
filename: "shortcode.png", filename: "shortcode.png",
path: "#{Pleroma.Config.get([:instance, :static_dir])}/add/shortcode.png" path: "#{Pleroma.Config.get([:instance, :static_dir])}/add/shortcode.png"
@ -322,7 +322,7 @@ test "new without shortcode", %{admin_conn: admin_conn} do
test "remove non existing shortcode in pack.json", %{admin_conn: admin_conn} do test "remove non existing shortcode in pack.json", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> delete("/api/pleroma/emoji/packs/test_pack/files?shortcode=blank3") |> delete("/api/pleroma/emoji/packs/files?name=test_pack&shortcode=blank3")
|> json_response_and_validate_schema(:bad_request) == %{ |> json_response_and_validate_schema(:bad_request) == %{
"error" => "Emoji \"blank3\" does not exist" "error" => "Emoji \"blank3\" does not exist"
} }
@ -331,7 +331,7 @@ test "remove non existing shortcode in pack.json", %{admin_conn: admin_conn} do
test "update non existing emoji", %{admin_conn: admin_conn} do test "update non existing emoji", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> put_req_header("content-type", "multipart/form-data") |> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/test_pack/files", %{ |> patch("/api/pleroma/emoji/packs/files?name=test_pack", %{
shortcode: "blank3", shortcode: "blank3",
new_shortcode: "blank4", new_shortcode: "blank4",
new_filename: "dir_2/blank_3.png" new_filename: "dir_2/blank_3.png"
@ -347,7 +347,7 @@ test "update with empty shortcode", %{admin_conn: admin_conn} do
} = } =
admin_conn admin_conn
|> put_req_header("content-type", "multipart/form-data") |> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/test_pack/files", %{ |> patch("/api/pleroma/emoji/packs/files?name=test_pack", %{
shortcode: "blank", shortcode: "blank",
new_filename: "dir_2/blank_3.png" new_filename: "dir_2/blank_3.png"
}) })

View File

@ -37,11 +37,11 @@ test "GET /api/pleroma/emoji/packs when :public: false", %{conn: conn} do
test "GET /api/pleroma/emoji/packs", %{conn: conn} do test "GET /api/pleroma/emoji/packs", %{conn: conn} do
resp = conn |> get("/api/pleroma/emoji/packs") |> json_response_and_validate_schema(200) resp = conn |> get("/api/pleroma/emoji/packs") |> json_response_and_validate_schema(200)
assert resp["count"] == 3 assert resp["count"] == 4
assert resp["packs"] assert resp["packs"]
|> Map.keys() |> Map.keys()
|> length() == 3 |> length() == 4
shared = resp["packs"]["test_pack"] shared = resp["packs"]["test_pack"]
assert shared["files"] == %{"blank" => "blank.png", "blank2" => "blank2.png"} assert shared["files"] == %{"blank" => "blank.png", "blank2" => "blank2.png"}
@ -58,7 +58,7 @@ test "GET /api/pleroma/emoji/packs", %{conn: conn} do
|> get("/api/pleroma/emoji/packs?page_size=1") |> get("/api/pleroma/emoji/packs?page_size=1")
|> json_response_and_validate_schema(200) |> json_response_and_validate_schema(200)
assert resp["count"] == 3 assert resp["count"] == 4
packs = Map.keys(resp["packs"]) packs = Map.keys(resp["packs"])
@ -71,7 +71,7 @@ test "GET /api/pleroma/emoji/packs", %{conn: conn} do
|> get("/api/pleroma/emoji/packs?page_size=1&page=2") |> get("/api/pleroma/emoji/packs?page_size=1&page=2")
|> json_response_and_validate_schema(200) |> json_response_and_validate_schema(200)
assert resp["count"] == 3 assert resp["count"] == 4
packs = Map.keys(resp["packs"]) packs = Map.keys(resp["packs"])
assert length(packs) == 1 assert length(packs) == 1
[pack2] = packs [pack2] = packs
@ -81,18 +81,28 @@ test "GET /api/pleroma/emoji/packs", %{conn: conn} do
|> get("/api/pleroma/emoji/packs?page_size=1&page=3") |> get("/api/pleroma/emoji/packs?page_size=1&page=3")
|> json_response_and_validate_schema(200) |> json_response_and_validate_schema(200)
assert resp["count"] == 3 assert resp["count"] == 4
packs = Map.keys(resp["packs"]) packs = Map.keys(resp["packs"])
assert length(packs) == 1 assert length(packs) == 1
[pack3] = packs [pack3] = packs
assert [pack1, pack2, pack3] |> Enum.uniq() |> length() == 3
resp =
conn
|> get("/api/pleroma/emoji/packs?page_size=1&page=4")
|> json_response_and_validate_schema(200)
assert resp["count"] == 4
packs = Map.keys(resp["packs"])
assert length(packs) == 1
[pack4] = packs
assert [pack1, pack2, pack3, pack4] |> Enum.uniq() |> length() == 4
end end
describe "GET /api/pleroma/emoji/packs/remote" do describe "GET /api/pleroma/emoji/packs/remote" do
test "shareable instance", %{admin_conn: admin_conn, conn: conn} do test "shareable instance", %{admin_conn: admin_conn, conn: conn} do
resp = resp =
conn conn
|> get("/api/pleroma/emoji/packs") |> get("/api/pleroma/emoji/packs?page=2&page_size=1")
|> json_response_and_validate_schema(200) |> json_response_and_validate_schema(200)
mock(fn mock(fn
@ -102,12 +112,12 @@ test "shareable instance", %{admin_conn: admin_conn, conn: conn} do
%{method: :get, url: "https://example.com/nodeinfo/2.1.json"} -> %{method: :get, url: "https://example.com/nodeinfo/2.1.json"} ->
json(%{metadata: %{features: ["shareable_emoji_packs"]}}) json(%{metadata: %{features: ["shareable_emoji_packs"]}})
%{method: :get, url: "https://example.com/api/pleroma/emoji/packs"} -> %{method: :get, url: "https://example.com/api/pleroma/emoji/packs?page=2&page_size=1"} ->
json(resp) json(resp)
end) end)
assert admin_conn assert admin_conn
|> get("/api/pleroma/emoji/packs/remote?url=https://example.com") |> get("/api/pleroma/emoji/packs/remote?url=https://example.com&page=2&page_size=1")
|> json_response_and_validate_schema(200) == resp |> json_response_and_validate_schema(200) == resp
end end
@ -128,11 +138,11 @@ test "non shareable instance", %{admin_conn: admin_conn} do
end end
end end
describe "GET /api/pleroma/emoji/packs/:name/archive" do describe "GET /api/pleroma/emoji/packs/archive?name=:name" do
test "download shared pack", %{conn: conn} do test "download shared pack", %{conn: conn} do
resp = resp =
conn conn
|> get("/api/pleroma/emoji/packs/test_pack/archive") |> get("/api/pleroma/emoji/packs/archive?name=test_pack")
|> response(200) |> response(200)
{:ok, arch} = :zip.unzip(resp, [:memory]) {:ok, arch} = :zip.unzip(resp, [:memory])
@ -143,7 +153,7 @@ test "download shared pack", %{conn: conn} do
test "non existing pack", %{conn: conn} do test "non existing pack", %{conn: conn} do
assert conn assert conn
|> get("/api/pleroma/emoji/packs/test_pack_for_import/archive") |> get("/api/pleroma/emoji/packs/archive?name=test_pack_for_import")
|> json_response_and_validate_schema(:not_found) == %{ |> json_response_and_validate_schema(:not_found) == %{
"error" => "Pack test_pack_for_import does not exist" "error" => "Pack test_pack_for_import does not exist"
} }
@ -151,7 +161,7 @@ test "non existing pack", %{conn: conn} do
test "non downloadable pack", %{conn: conn} do test "non downloadable pack", %{conn: conn} do
assert conn assert conn
|> get("/api/pleroma/emoji/packs/test_pack_nonshared/archive") |> get("/api/pleroma/emoji/packs/archive?name=test_pack_nonshared")
|> json_response_and_validate_schema(:forbidden) == %{ |> json_response_and_validate_schema(:forbidden) == %{
"error" => "error" =>
"Pack test_pack_nonshared cannot be downloaded from this instance, either pack sharing was disabled for this pack or some files are missing" "Pack test_pack_nonshared cannot be downloaded from this instance, either pack sharing was disabled for this pack or some files are missing"
@ -173,28 +183,28 @@ test "shared pack from remote and non shared from fallback-src", %{
%{ %{
method: :get, method: :get,
url: "https://example.com/api/pleroma/emoji/packs/test_pack" url: "https://example.com/api/pleroma/emoji/pack?name=test_pack"
} -> } ->
conn conn
|> get("/api/pleroma/emoji/packs/test_pack") |> get("/api/pleroma/emoji/pack?name=test_pack")
|> json_response_and_validate_schema(200) |> json_response_and_validate_schema(200)
|> json() |> json()
%{ %{
method: :get, method: :get,
url: "https://example.com/api/pleroma/emoji/packs/test_pack/archive" url: "https://example.com/api/pleroma/emoji/packs/archive?name=test_pack"
} -> } ->
conn conn
|> get("/api/pleroma/emoji/packs/test_pack/archive") |> get("/api/pleroma/emoji/packs/archive?name=test_pack")
|> response(200) |> response(200)
|> text() |> text()
%{ %{
method: :get, method: :get,
url: "https://example.com/api/pleroma/emoji/packs/test_pack_nonshared" url: "https://example.com/api/pleroma/emoji/pack?name=test_pack_nonshared"
} -> } ->
conn conn
|> get("/api/pleroma/emoji/packs/test_pack_nonshared") |> get("/api/pleroma/emoji/pack?name=test_pack_nonshared")
|> json_response_and_validate_schema(200) |> json_response_and_validate_schema(200)
|> json() |> json()
@ -218,7 +228,7 @@ test "shared pack from remote and non shared from fallback-src", %{
assert File.exists?("#{@emoji_path}/test_pack2/blank.png") assert File.exists?("#{@emoji_path}/test_pack2/blank.png")
assert admin_conn assert admin_conn
|> delete("/api/pleroma/emoji/packs/test_pack2") |> delete("/api/pleroma/emoji/pack?name=test_pack2")
|> json_response_and_validate_schema(200) == "ok" |> json_response_and_validate_schema(200) == "ok"
refute File.exists?("#{@emoji_path}/test_pack2") refute File.exists?("#{@emoji_path}/test_pack2")
@ -239,7 +249,7 @@ test "shared pack from remote and non shared from fallback-src", %{
assert File.exists?("#{@emoji_path}/test_pack_nonshared2/blank.png") assert File.exists?("#{@emoji_path}/test_pack_nonshared2/blank.png")
assert admin_conn assert admin_conn
|> delete("/api/pleroma/emoji/packs/test_pack_nonshared2") |> delete("/api/pleroma/emoji/pack?name=test_pack_nonshared2")
|> json_response_and_validate_schema(200) == "ok" |> json_response_and_validate_schema(200) == "ok"
refute File.exists?("#{@emoji_path}/test_pack_nonshared2") refute File.exists?("#{@emoji_path}/test_pack_nonshared2")
@ -279,14 +289,14 @@ test "checksum fail", %{admin_conn: admin_conn} do
%{ %{
method: :get, method: :get,
url: "https://example.com/api/pleroma/emoji/packs/pack_bad_sha" url: "https://example.com/api/pleroma/emoji/pack?name=pack_bad_sha"
} -> } ->
{:ok, pack} = Pleroma.Emoji.Pack.load_pack("pack_bad_sha") {:ok, pack} = Pleroma.Emoji.Pack.load_pack("pack_bad_sha")
%Tesla.Env{status: 200, body: Jason.encode!(pack)} %Tesla.Env{status: 200, body: Jason.encode!(pack)}
%{ %{
method: :get, method: :get,
url: "https://example.com/api/pleroma/emoji/packs/pack_bad_sha/archive" url: "https://example.com/api/pleroma/emoji/packs/archive?name=pack_bad_sha"
} -> } ->
%Tesla.Env{ %Tesla.Env{
status: 200, status: 200,
@ -316,7 +326,7 @@ test "other error", %{admin_conn: admin_conn} do
%{ %{
method: :get, method: :get,
url: "https://example.com/api/pleroma/emoji/packs/test_pack" url: "https://example.com/api/pleroma/emoji/pack?name=test_pack"
} -> } ->
{:ok, pack} = Pleroma.Emoji.Pack.load_pack("test_pack") {:ok, pack} = Pleroma.Emoji.Pack.load_pack("test_pack")
%Tesla.Env{status: 200, body: Jason.encode!(pack)} %Tesla.Env{status: 200, body: Jason.encode!(pack)}
@ -336,7 +346,7 @@ test "other error", %{admin_conn: admin_conn} do
end end
end end
describe "PATCH /api/pleroma/emoji/packs/:name" do describe "PATCH /api/pleroma/emoji/pack?name=:name" do
setup do setup do
pack_file = "#{@emoji_path}/test_pack/pack.json" pack_file = "#{@emoji_path}/test_pack/pack.json"
original_content = File.read!(pack_file) original_content = File.read!(pack_file)
@ -358,7 +368,9 @@ test "other error", %{admin_conn: admin_conn} do
test "for a pack without a fallback source", ctx do test "for a pack without a fallback source", ctx do
assert ctx[:admin_conn] assert ctx[:admin_conn]
|> put_req_header("content-type", "multipart/form-data") |> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/test_pack", %{"metadata" => ctx[:new_data]}) |> patch("/api/pleroma/emoji/pack?name=test_pack", %{
"metadata" => ctx[:new_data]
})
|> json_response_and_validate_schema(200) == ctx[:new_data] |> json_response_and_validate_schema(200) == ctx[:new_data]
assert Jason.decode!(File.read!(ctx[:pack_file]))["pack"] == ctx[:new_data] assert Jason.decode!(File.read!(ctx[:pack_file]))["pack"] == ctx[:new_data]
@ -384,7 +396,7 @@ test "for a pack with a fallback source", ctx do
assert ctx[:admin_conn] assert ctx[:admin_conn]
|> put_req_header("content-type", "multipart/form-data") |> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/test_pack", %{metadata: new_data}) |> patch("/api/pleroma/emoji/pack?name=test_pack", %{metadata: new_data})
|> json_response_and_validate_schema(200) == new_data_with_sha |> json_response_and_validate_schema(200) == new_data_with_sha
assert Jason.decode!(File.read!(ctx[:pack_file]))["pack"] == new_data_with_sha assert Jason.decode!(File.read!(ctx[:pack_file]))["pack"] == new_data_with_sha
@ -404,17 +416,17 @@ test "when the fallback source doesn't have all the files", ctx do
assert ctx[:admin_conn] assert ctx[:admin_conn]
|> put_req_header("content-type", "multipart/form-data") |> put_req_header("content-type", "multipart/form-data")
|> patch("/api/pleroma/emoji/packs/test_pack", %{metadata: new_data}) |> patch("/api/pleroma/emoji/pack?name=test_pack", %{metadata: new_data})
|> json_response_and_validate_schema(:bad_request) == %{ |> json_response_and_validate_schema(:bad_request) == %{
"error" => "The fallback archive does not have all files specified in pack.json" "error" => "The fallback archive does not have all files specified in pack.json"
} }
end end
end end
describe "POST/DELETE /api/pleroma/emoji/packs/:name" do describe "POST/DELETE /api/pleroma/emoji/pack?name=:name" do
test "creating and deleting a pack", %{admin_conn: admin_conn} do test "creating and deleting a pack", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> post("/api/pleroma/emoji/packs/test_created") |> post("/api/pleroma/emoji/pack?name=test_created")
|> json_response_and_validate_schema(200) == "ok" |> json_response_and_validate_schema(200) == "ok"
assert File.exists?("#{@emoji_path}/test_created/pack.json") assert File.exists?("#{@emoji_path}/test_created/pack.json")
@ -426,7 +438,7 @@ test "creating and deleting a pack", %{admin_conn: admin_conn} do
} }
assert admin_conn assert admin_conn
|> delete("/api/pleroma/emoji/packs/test_created") |> delete("/api/pleroma/emoji/pack?name=test_created")
|> json_response_and_validate_schema(200) == "ok" |> json_response_and_validate_schema(200) == "ok"
refute File.exists?("#{@emoji_path}/test_created/pack.json") refute File.exists?("#{@emoji_path}/test_created/pack.json")
@ -439,7 +451,7 @@ test "if pack exists", %{admin_conn: admin_conn} do
File.write!(Path.join(path, "pack.json"), pack_file) File.write!(Path.join(path, "pack.json"), pack_file)
assert admin_conn assert admin_conn
|> post("/api/pleroma/emoji/packs/test_created") |> post("/api/pleroma/emoji/pack?name=test_created")
|> json_response_and_validate_schema(:conflict) == %{ |> json_response_and_validate_schema(:conflict) == %{
"error" => "A pack named \"test_created\" already exists" "error" => "A pack named \"test_created\" already exists"
} }
@ -449,7 +461,7 @@ test "if pack exists", %{admin_conn: admin_conn} do
test "with empty name", %{admin_conn: admin_conn} do test "with empty name", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> post("/api/pleroma/emoji/packs/ ") |> post("/api/pleroma/emoji/pack?name= ")
|> json_response_and_validate_schema(:bad_request) == %{ |> json_response_and_validate_schema(:bad_request) == %{
"error" => "pack name cannot be empty" "error" => "pack name cannot be empty"
} }
@ -458,7 +470,7 @@ test "with empty name", %{admin_conn: admin_conn} do
test "deleting nonexisting pack", %{admin_conn: admin_conn} do test "deleting nonexisting pack", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> delete("/api/pleroma/emoji/packs/non_existing") |> delete("/api/pleroma/emoji/pack?name=non_existing")
|> json_response_and_validate_schema(:not_found) == %{ |> json_response_and_validate_schema(:not_found) == %{
"error" => "Pack non_existing does not exist" "error" => "Pack non_existing does not exist"
} }
@ -466,7 +478,7 @@ test "deleting nonexisting pack", %{admin_conn: admin_conn} do
test "deleting with empty name", %{admin_conn: admin_conn} do test "deleting with empty name", %{admin_conn: admin_conn} do
assert admin_conn assert admin_conn
|> delete("/api/pleroma/emoji/packs/ ") |> delete("/api/pleroma/emoji/pack?name= ")
|> json_response_and_validate_schema(:bad_request) == %{ |> json_response_and_validate_schema(:bad_request) == %{
"error" => "pack name cannot be empty" "error" => "pack name cannot be empty"
} }
@ -514,7 +526,7 @@ test "filesystem import", %{admin_conn: admin_conn, conn: conn} do
} }
end end
describe "GET /api/pleroma/emoji/packs/:name" do describe "GET /api/pleroma/emoji/pack?name=:name" do
test "shows pack.json", %{conn: conn} do test "shows pack.json", %{conn: conn} do
assert %{ assert %{
"files" => files, "files" => files,
@ -529,7 +541,7 @@ test "shows pack.json", %{conn: conn} do
} }
} = } =
conn conn
|> get("/api/pleroma/emoji/packs/test_pack") |> get("/api/pleroma/emoji/pack?name=test_pack")
|> json_response_and_validate_schema(200) |> json_response_and_validate_schema(200)
assert files == %{"blank" => "blank.png", "blank2" => "blank2.png"} assert files == %{"blank" => "blank.png", "blank2" => "blank2.png"}
@ -539,7 +551,7 @@ test "shows pack.json", %{conn: conn} do
"files_count" => 2 "files_count" => 2
} = } =
conn conn
|> get("/api/pleroma/emoji/packs/test_pack?page_size=1") |> get("/api/pleroma/emoji/pack?name=test_pack&page_size=1")
|> json_response_and_validate_schema(200) |> json_response_and_validate_schema(200)
assert files |> Map.keys() |> length() == 1 assert files |> Map.keys() |> length() == 1
@ -549,15 +561,33 @@ test "shows pack.json", %{conn: conn} do
"files_count" => 2 "files_count" => 2
} = } =
conn conn
|> get("/api/pleroma/emoji/packs/test_pack?page_size=1&page=2") |> get("/api/pleroma/emoji/pack?name=test_pack&page_size=1&page=2")
|> json_response_and_validate_schema(200) |> json_response_and_validate_schema(200)
assert files |> Map.keys() |> length() == 1 assert files |> Map.keys() |> length() == 1
end end
test "for pack name with special chars", %{conn: conn} do
assert %{
"files" => files,
"files_count" => 1,
"pack" => %{
"can-download" => true,
"description" => "Test description",
"download-sha256" => _,
"homepage" => "https://pleroma.social",
"license" => "Test license",
"share-files" => true
}
} =
conn
|> get("/api/pleroma/emoji/pack?name=blobs.gg")
|> json_response_and_validate_schema(200)
end
test "non existing pack", %{conn: conn} do test "non existing pack", %{conn: conn} do
assert conn assert conn
|> get("/api/pleroma/emoji/packs/non_existing") |> get("/api/pleroma/emoji/pack?name=non_existing")
|> json_response_and_validate_schema(:not_found) == %{ |> json_response_and_validate_schema(:not_found) == %{
"error" => "Pack non_existing does not exist" "error" => "Pack non_existing does not exist"
} }
@ -565,7 +595,7 @@ test "non existing pack", %{conn: conn} do
test "error name", %{conn: conn} do test "error name", %{conn: conn} do
assert conn assert conn
|> get("/api/pleroma/emoji/packs/ ") |> get("/api/pleroma/emoji/pack?name= ")
|> json_response_and_validate_schema(:bad_request) == %{ |> json_response_and_validate_schema(:bad_request) == %{
"error" => "pack name cannot be empty" "error" => "pack name cannot be empty"
} }